<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
    <head>
        <!-- Book generated using mdBook -->
        <meta charset="UTF-8">
        <title></title>
        
        <meta name="robots" content="noindex" />
        
        


        <!-- Custom HTML head -->
        


        <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="theme-color" content="#ffffff" />

        
        <link rel="icon" href="favicon.svg">
        
        
        <link rel="shortcut icon" href="favicon.png">
        
        <link rel="stylesheet" href="css/variables.css">
        <link rel="stylesheet" href="css/general.css">
        <link rel="stylesheet" href="css/chrome.css">
        
        <link rel="stylesheet" href="css/print.css" media="print">
        

        <!-- Fonts -->
        <link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
        
        <link rel="stylesheet" href="fonts/fonts.css">
        

        <!-- Highlight.js Stylesheets -->
        <link rel="stylesheet" href="highlight.css">
        <link rel="stylesheet" href="tomorrow-night.css">
        <link rel="stylesheet" href="ayu-highlight.css">

        <!-- Custom theme stylesheets -->
        

        
    </head>
    <body>
        <!-- Provide site root to javascript -->
        <script type="text/javascript">
            var path_to_root = "";
            var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
        </script>

        <!-- Work around some values being stored in localStorage wrapped in quotes -->
        <script type="text/javascript">
            try {
                var theme = localStorage.getItem('mdbook-theme');
                var sidebar = localStorage.getItem('mdbook-sidebar');

                if (theme.startsWith('"') && theme.endsWith('"')) {
                    localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
                }

                if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
                    localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
                }
            } catch (e) { }
        </script>

        <!-- Set the theme before any content is loaded, prevents flash -->
        <script type="text/javascript">
            var theme;
            try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
            if (theme === null || theme === undefined) { theme = default_theme; }
            var html = document.querySelector('html');
            html.classList.remove('no-js')
            html.classList.remove('light')
            html.classList.add(theme);
            html.classList.add('js');
        </script>

        <!-- Hide / unhide sidebar before it is displayed -->
        <script type="text/javascript">
            var html = document.querySelector('html');
            var sidebar = 'hidden';
            if (document.body.clientWidth >= 1080) {
                try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
                sidebar = sidebar || 'visible';
            }
            html.classList.remove('sidebar-visible');
            html.classList.add("sidebar-" + sidebar);
        </script>

        <nav id="sidebar" class="sidebar" aria-label="Table of contents">
            <div class="sidebar-scrollbox">
                <ol class="chapter"><li class="chapter-item expanded affix "><a href="目录.html">目录</a></li><li class="chapter-item expanded "><a href="如何阅读代码.html"><strong aria-hidden="true">1.</strong> 如何阅读代码</a></li><li class="chapter-item expanded "><a href="应该知道的Linux技巧.html"><strong aria-hidden="true">2.</strong> 应该知道的Linux技巧</a></li><li class="chapter-item expanded "><a href="bash/index.html"><strong aria-hidden="true">3.</strong> bash</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="bash/8个实用而有趣Bash命令提示行.html"><strong aria-hidden="true">3.1.</strong> 8个实用而有趣Bash命令提示行</a></li><li class="chapter-item expanded "><a href="bash/Jobs.html"><strong aria-hidden="true">3.2.</strong> Jobs</a></li><li class="chapter-item expanded "><a href="bash/ShellScript语法一.html"><strong aria-hidden="true">3.3.</strong> ShellScript语法一</a></li><li class="chapter-item expanded "><a href="bash/ShellScript语法二.html"><strong aria-hidden="true">3.4.</strong> ShellScript语法二</a></li><li class="chapter-item expanded "><a href="bash/argument_parameter_option.html"><strong aria-hidden="true">3.5.</strong> argument_parameter_option</a></li><li class="chapter-item expanded "><a href="bash/如何调试bash脚本.html"><strong aria-hidden="true">3.6.</strong> 如何调试bash脚本</a></li><li class="chapter-item expanded "><a href="bash/cheat_sheet/index.html"><strong aria-hidden="true">3.7.</strong> cheat_sheet</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="bash/cheat_sheet/sh.html"><strong aria-hidden="true">3.7.1.</strong> sh</a></li></ol></li></ol></li><li class="chapter-item expanded "><a href="cmd/index.html"><strong aria-hidden="true">4.</strong> cmd</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="cmd/awk.html"><strong aria-hidden="true">4.1.</strong> awk</a></li><li class="chapter-item expanded "><a href="cmd/nm.html"><strong aria-hidden="true">4.2.</strong> nm</a></li><li class="chapter-item expanded "><a href="cmd/strace_ltrace.html"><strong aria-hidden="true">4.3.</strong> strace_ltrace</a></li><li class="chapter-item expanded "><a href="cmd/tcpdump.html"><strong aria-hidden="true">4.4.</strong> tcpdump</a></li><li class="chapter-item expanded "><a href="cmd/cheat_sheet/index.html"><strong aria-hidden="true">4.5.</strong> cheat_sheet</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="cmd/cheat_sheet/awk.html"><strong aria-hidden="true">4.5.1.</strong> awk</a></li><li class="chapter-item expanded "><a href="cmd/cheat_sheet/tcpdump.html"><strong aria-hidden="true">4.5.2.</strong> tcpdump</a></li></ol></li></ol></li><li class="chapter-item expanded "><a href="fileSystem/index.html"><strong aria-hidden="true">5.</strong> fileSystem</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="fileSystem/文件系统.html"><strong aria-hidden="true">5.1.</strong> 文件系统</a></li><li class="chapter-item expanded "><a href="fileSystem/proc/index.html"><strong aria-hidden="true">5.2.</strong> proc</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="fileSystem/proc/[pid]_fd_socket.html"><strong aria-hidden="true">5.2.1.</strong> [pid]_fd_socket</a></li><li class="chapter-item expanded "><a href="fileSystem/proc/[pid]_status.html"><strong aria-hidden="true">5.2.2.</strong> [pid]_status</a></li><li class="chapter-item expanded "><a href="fileSystem/proc/net.html"><strong aria-hidden="true">5.2.3.</strong> net</a></li><li class="chapter-item expanded "><a href="fileSystem/proc/proc浅谈.html"><strong aria-hidden="true">5.2.4.</strong> proc浅谈</a></li></ol></li></ol></li><li class="chapter-item expanded "><a href="gcc_gdb/index.html"><strong aria-hidden="true">6.</strong> gcc_gdb</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="gcc_gdb/gdb的基本使用.html"><strong aria-hidden="true">6.1.</strong> gdb的基本使用</a></li></ol></li><li class="chapter-item expanded "><a href="git/index.html"><strong aria-hidden="true">7.</strong> git</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="git/1_git基本要点.html"><strong aria-hidden="true">7.1.</strong> 1_git基本要点</a></li><li class="chapter-item expanded "><a href="git/2_git配置.html"><strong aria-hidden="true">7.2.</strong> 2_git配置</a></li><li class="chapter-item expanded "><a href="git/4_git本地操作.html"><strong aria-hidden="true">7.3.</strong> 4_git本地操作</a></li><li class="chapter-item expanded "><a href="git/5_gitignore.html"><strong aria-hidden="true">7.4.</strong> 5_gitignore</a></li><li class="chapter-item expanded "><a href="git/6_git分支操作.html"><strong aria-hidden="true">7.5.</strong> 6_git分支操作</a></li><li class="chapter-item expanded "><a href="git/7_git远程仓库.html"><strong aria-hidden="true">7.6.</strong> 7_git远程仓库</a></li><li class="chapter-item expanded "><a href="git/8_git通信协议.html"><strong aria-hidden="true">7.7.</strong> 8_git通信协议</a></li><li class="chapter-item expanded "><a href="git/9_git服务器搭建.html"><strong aria-hidden="true">7.8.</strong> 9_git服务器搭建</a></li><li class="chapter-item expanded "><a href="git/diff输出格式.html"><strong aria-hidden="true">7.9.</strong> diff输出格式</a></li><li class="chapter-item expanded "><a href="git/git使用小技巧.html"><strong aria-hidden="true">7.10.</strong> git使用小技巧</a></li><li class="chapter-item expanded "><a href="git/git分支.html"><strong aria-hidden="true">7.11.</strong> git分支</a></li><li class="chapter-item expanded "><a href="git/git提供的工具.html"><strong aria-hidden="true">7.12.</strong> git提供的工具</a></li><li class="chapter-item expanded "><a href="git/git衍合.html"><strong aria-hidden="true">7.13.</strong> git衍合</a></li><li class="chapter-item expanded "><a href="git/extend/index.html"><strong aria-hidden="true">7.14.</strong> extend</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="git/extend/git_http请求分析.html"><strong aria-hidden="true">7.14.1.</strong> git_http请求分析</a></li></ol></li></ol></li><li class="chapter-item expanded "><a href="gpg/index.html"><strong aria-hidden="true">8.</strong> gpg</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="gpg/gpg的简单使用.html"><strong aria-hidden="true">8.1.</strong> gpg的简单使用</a></li></ol></li><li class="chapter-item expanded "><a href="language/index.html"><strong aria-hidden="true">9.</strong> language</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="language/empty.html"><strong aria-hidden="true">9.1.</strong> empty</a></li><li class="chapter-item expanded "><a href="language/c_plus_plus/index.html"><strong aria-hidden="true">9.2.</strong> c_plus_plus</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="language/c_plus_plus/4个类型转换关键字.html"><strong aria-hidden="true">9.2.1.</strong> 4个类型转换关键字</a></li><li class="chapter-item expanded "><a href="language/c_plus_plus/RTTI.html"><strong aria-hidden="true">9.2.2.</strong> RTTI</a></li><li class="chapter-item expanded "><a href="language/c_plus_plus/const.html"><strong aria-hidden="true">9.2.3.</strong> const</a></li><li class="chapter-item expanded "><a href="language/c_plus_plus/内存对齐.html"><strong aria-hidden="true">9.2.4.</strong> 内存对齐</a></li><li class="chapter-item expanded "><a href="language/c_plus_plus/拷贝赋值.html"><strong aria-hidden="true">9.2.5.</strong> 拷贝赋值</a></li><li class="chapter-item expanded "><a href="language/c_plus_plus/指针.html"><strong aria-hidden="true">9.2.6.</strong> 指针</a></li><li class="chapter-item expanded "><a href="language/c_plus_plus/构造析构.html"><strong aria-hidden="true">9.2.7.</strong> 构造析构</a></li><li class="chapter-item expanded "><a href="language/c_plus_plus/私有不可访问吗.html"><strong aria-hidden="true">9.2.8.</strong> 私有不可访问吗</a></li><li class="chapter-item expanded "><a href="language/c_plus_plus/继承.html"><strong aria-hidden="true">9.2.9.</strong> 继承</a></li><li class="chapter-item expanded "><a href="language/c_plus_plus/虚函数.html"><strong aria-hidden="true">9.2.10.</strong> 虚函数</a></li><li class="chapter-item expanded "><a href="language/c_plus_plus/静态库的链接.html"><strong aria-hidden="true">9.2.11.</strong> 静态库的链接</a></li></ol></li><li class="chapter-item expanded "><a href="language/python/index.html"><strong aria-hidden="true">9.3.</strong> python</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="language/python/optparse的简单使用.html"><strong aria-hidden="true">9.3.1.</strong> optparse的简单使用</a></li></ol></li></ol></li><li class="chapter-item expanded "><a href="linux编程/index.html"><strong aria-hidden="true">10.</strong> linux编程</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="linux编程/常用开发工具.html"><strong aria-hidden="true">10.1.</strong> 常用开发工具</a></li><li class="chapter-item expanded "><a href="linux编程/线程.html"><strong aria-hidden="true">10.2.</strong> 线程</a></li><li class="chapter-item expanded "><a href="linux编程/进程.html"><strong aria-hidden="true">10.3.</strong> 进程</a></li><li class="chapter-item expanded "><a href="linux编程/fs/index.html"><strong aria-hidden="true">10.4.</strong> fs</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="linux编程/fs/dir.html"><strong aria-hidden="true">10.4.1.</strong> dir</a></li><li class="chapter-item expanded "><a href="linux编程/fs/file.html"><strong aria-hidden="true">10.4.2.</strong> file</a></li><li class="chapter-item expanded "><a href="linux编程/fs/getcwd_chdir.html"><strong aria-hidden="true">10.4.3.</strong> getcwd_chdir</a></li><li class="chapter-item expanded "><a href="linux编程/fs/stat.html"><strong aria-hidden="true">10.4.4.</strong> stat</a></li></ol></li></ol></li><li class="chapter-item expanded "><a href="make/index.html"><strong aria-hidden="true">11.</strong> make</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="make/Makefile.html"><strong aria-hidden="true">11.1.</strong> Makefile</a></li></ol></li><li class="chapter-item expanded "><a href="other/index.html"><strong aria-hidden="true">12.</strong> other</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="other/linux设置代理.html"><strong aria-hidden="true">12.1.</strong> linux设置代理</a></li><li class="chapter-item expanded "><a href="other/负数的补码怎么得到.html"><strong aria-hidden="true">12.2.</strong> 负数的补码怎么得到</a></li><li class="chapter-item expanded "><a href="other/缓存/index.html"><strong aria-hidden="true">12.3.</strong> 缓存</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="other/缓存/缓存穿透_击穿_雪崩.html"><strong aria-hidden="true">12.3.1.</strong> 缓存穿透_击穿_雪崩</a></li></ol></li></ol></li><li class="chapter-item expanded "><a href="reference/index.html"><strong aria-hidden="true">13.</strong> reference</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="reference/MEMORY_ALIGNMENT_ISSUE.html"><strong aria-hidden="true">13.1.</strong> MEMORY_ALIGNMENT_ISSUE</a></li></ol></li><li class="chapter-item expanded "><a href="small_program/index.html"><strong aria-hidden="true">14.</strong> small_program</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="small_program/程序模块.html"><strong aria-hidden="true">14.1.</strong> 程序模块</a></li></ol></li><li class="chapter-item expanded "><a href="ssh/index.html"><strong aria-hidden="true">15.</strong> ssh</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ssh/ssh的高级使用方式.html"><strong aria-hidden="true">15.1.</strong> ssh的高级使用方式</a></li><li class="chapter-item expanded "><a href="ssh/ssh私钥登录.html"><strong aria-hidden="true">15.2.</strong> ssh私钥登录</a></li></ol></li><li class="chapter-item expanded "><a href="ubuntu/index.html"><strong aria-hidden="true">16.</strong> ubuntu</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ubuntu/启动器文件.html"><strong aria-hidden="true">16.1.</strong> 启动器文件</a></li></ol></li><li class="chapter-item expanded "><a href="x/index.html"><strong aria-hidden="true">17.</strong> x</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="x/Linux下的X是什么.html"><strong aria-hidden="true">17.1.</strong> Linux下的X是什么</a></li><li class="chapter-item expanded "><a href="x/Win10安装VCXSRV.html"><strong aria-hidden="true">17.2.</strong> Win10安装VCXSRV</a></li><li class="chapter-item expanded "><a href="x/XApplication配置.html"><strong aria-hidden="true">17.3.</strong> XApplication配置</a></li><li class="chapter-item expanded "><a href="x/startx与xinit.html"><strong aria-hidden="true">17.4.</strong> startx与xinit</a></li></ol></li><li class="chapter-item expanded "><a href="技术名词/index.html"><strong aria-hidden="true">18.</strong> 技术名词</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="技术名词/cheat_sheet.html"><strong aria-hidden="true">18.1.</strong> cheat_sheet</a></li></ol></li><li class="chapter-item expanded "><a href="网络/index.html"><strong aria-hidden="true">19.</strong> 网络</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="网络/linux网络.html"><strong aria-hidden="true">19.1.</strong> linux网络</a></li><li class="chapter-item expanded "><a href="网络/标准的简单网络服务.html"><strong aria-hidden="true">19.2.</strong> 标准的简单网络服务</a></li><li class="chapter-item expanded "><a href="网络/计算机网络.html"><strong aria-hidden="true">19.3.</strong> 计算机网络</a></li><li class="chapter-item expanded "><a href="网络/tcp/index.html"><strong aria-hidden="true">19.4.</strong> tcp</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="网络/tcp/tcpKeepAlive与复位报文.html"><strong aria-hidden="true">19.4.1.</strong> tcpKeepAlive与复位报文</a></li><li class="chapter-item expanded "><a href="网络/tcp/tcpKeepAlive与应用层keepAlive.html"><strong aria-hidden="true">19.4.2.</strong> tcpKeepAlive与应用层keepAlive</a></li><li class="chapter-item expanded "><a href="网络/tcp/tcp简介.html"><strong aria-hidden="true">19.4.3.</strong> tcp简介</a></li></ol></li></ol></li><li class="chapter-item expanded "><a href="装机高手/index.html"><strong aria-hidden="true">20.</strong> 装机高手</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="装机高手/empty.html"><strong aria-hidden="true">20.1.</strong> empty</a></li></ol></li></ol>
            </div>
            <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
        </nav>

        <div id="page-wrapper" class="page-wrapper">

            <div class="page">
                
                <div id="menu-bar-hover-placeholder"></div>
                <div id="menu-bar" class="menu-bar sticky bordered">
                    <div class="left-buttons">
                        <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
                            <i class="fa fa-bars"></i>
                        </button>
                        <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
                            <i class="fa fa-paint-brush"></i>
                        </button>
                        <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
                            <li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
                        </ul>
                        
                        <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
                            <i class="fa fa-search"></i>
                        </button>
                        
                    </div>

                    <h1 class="menu-title"></h1>

                    <div class="right-buttons">
                        
                        <a href="print.html" title="Print this book" aria-label="Print this book">
                            <i id="print-button" class="fa fa-print"></i>
                        </a>
                        
                        
                    </div>
                </div>

                
                <div id="search-wrapper" class="hidden">
                    <form id="searchbar-outer" class="searchbar-outer">
                        <input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
                    </form>
                    <div id="searchresults-outer" class="searchresults-outer hidden">
                        <div id="searchresults-header" class="searchresults-header"></div>
                        <ul id="searchresults">
                        </ul>
                    </div>
                </div>
                

                <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
                <script type="text/javascript">
                    document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
                    document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
                    Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
                        link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
                    });
                </script>

                <div id="content" class="content">
                    <main>
                        <h2 id="normal模块"><a class="header" href="#normal模块">normal模块</a></h2>
<ul>
<li><a href="./%E5%A6%82%E4%BD%95%E9%98%85%E8%AF%BB%E4%BB%A3%E7%A0%81.html">如何阅读代码</a></li>
<li><a href="./%E5%BA%94%E8%AF%A5%E7%9F%A5%E9%81%93%E7%9A%84Linux%E6%8A%80%E5%B7%A7.html">应该知道的Linux技巧</a></li>
</ul>
<ul>
<li>流程细节分析</li>
<li>单流程逻辑上有没有问题</li>
<li>多线程高并发下程序有没有问题</li>
<li>多站点(进程)场景</li>
</ul>
<h1 id="流程细节分析"><a class="header" href="#流程细节分析">流程细节分析</a></h1>
<p>读代码首先要整明白代码的功能，它是干什么的，怎么干的。这个时候要带着没有任何bug的假设来读它。</p>
<h1 id="单流程逻辑上有没有问题"><a class="header" href="#单流程逻辑上有没有问题">单流程逻辑上有没有问题</a></h1>
<p>读明白代码的功能之后，现在要带着代码中有bug的假设来读它，首先要看看单线程情况下，代码逻辑有没有bug。单线程的问题还是比较容易发现，因为我们平时读代码时，整个流程下来，就是一个单线程运行的流程，大部分都是逻辑上的问题。</p>
<h1 id="多线程高并发下程序有没有问题"><a class="header" href="#多线程高并发下程序有没有问题">多线程高并发下程序有没有问题</a></h1>
<p>思考完单线程下的运行情况之后，我们再来看看多线程高并发有没有问题，平时不注意，潜意识里没有考虑高并发下的情况，单线程下看着没问题的代码，放到高并发的应用场景就很有可能出现问题，如临界资源没有上锁，临界资源修改的顺序等等，都会影响到程序的正确运行。</p>
<p>读代码时老是忘了考虑高并发情况......</p>
<p>特别是业务逻辑本身就很复杂的情况下，把精力都放到理解功能上去了，就会疏忽程序高并发下的正确性。</p>
<h1 id="多站点进程场景"><a class="header" href="#多站点进程场景">多站点(进程)场景</a></h1>
<p>多站点不一定是在多台机器上，在一台机器上启动了多进程也是多站点。</p>
<p>一方面多站点场景和多线程场景很像，如果临界资源没处理好，就会出现问题，但是有时候多线程情况下运行正确，多站点场景也可能出现问题，如在多线程情况下对某一个全局资源缓存操作，线程间可以通过锁来保证缓存数据的正确性，但是不同进程之间没有考虑好同一全局资源的操作的话，也会产生资源的脏读问题。</p>
<h1 id="应该知道的linux技巧"><a class="header" href="#应该知道的linux技巧">应该知道的Linux技巧</a></h1>
<p>这篇文章来源于Quroa的一个问答《What are some time-saving tips that every Linux user should know?》—— Linux用户有哪些应该知道的提高效率的技巧。我觉得挺好的，总结得比较好，把其转过来，并加了一些自己的理解。 首先，我想告诉大家，在Unix/Linux下，最有效率技巧的不是操作图形界面，而是命令行操作，因为命令行意味着自动化。如果你看过《你可能不知道的Shell》以及《28个Unix/Linux的命令行神器》你就会知道Linux有多强大，这个强大完全来自于命令行，于是，就算你不知道怎么去做一个环保主义的程序员，至少他们可以让你少熬点夜，从而有利于你的身体健康和性生活。下面是一个有点长的列表，正如作者所说，你并不需要知道所有的这些东西，但是如果你还在很沉重地在使用Linux的话，这些东西都值得你看一看。 （注：如果你想知道下面涉及到的命令的更多的用法，你一定要man一点。对于一些命令，你可以需要先yum或apt-get来安装一下，如果有什么问题，别忘了Google。如果你要Baidu的话，我仅代表这个地球上所有的生物包括微生物甚至细菌病毒和小强BS你到宇宙毁灭）</p>
<h2 id="基础"><a class="header" href="#基础">基础</a></h2>
<ul>
<li>学习 Bash。你可以man bash来看看bash的东西，并不复杂也并不长。你用别的shell也行，但是bash是很强大的并且也是系统默认的。（学习zsh或tsch只会让你在很多情况下受到限制）</li>
<li>学习 vim。在Linux下，基本没有什么可与之竞争的编译辑器（就算你是一个Emacs或Eclipse的重度用户）。你可以看看《简明vim攻略》和 《Vim的冒险游戏》以及《给程序员的Vim速查卡》还有《把Vim变成一个编程的IDE》等等。</li>
<li>了解 ssh。明白不需要口令的用户认证（通过ssh-agent, ssh-add），学会用ssh翻墙，用scp而不是ftp传文件，等等。你知道吗？scp 远端的时候，你可以按tab键来查看远端的目录和文件（当然，需要无口令的用户认证），这都是bash的功劳。</li>
<li>熟悉bash的作业管理，如： &amp;, Ctrl-Z, Ctrl-C, jobs, fg, bg, kill, 等等。当然，你也要知道Ctrl+\（SIGQUIT）和Ctrl+C （SIGINT）的区别。</li>
<li>简单的文件管理 ： ls 和 ls -l (你最好知道 “ls -l” 的每一列的意思), less, head, tail 和 tail -f, ln 和 ln -s (你知道明白hard link和soft link的不同和优缺点), chown, chmod, du (如果你想看看磁盘的大小 du -sk *), df, mount。当然，原作者忘了find命令。</li>
<li>基础的网络管理： ip 或 ifconfig, dig。当然，原作者还忘了如netstat, ping, traceroute, 等</li>
<li>理解正则表达式，还有grep/egrep的各种选项。比如： -o, -A, 和 -B 这些选项是很值得了解的。</li>
<li>学习使用 apt-get 和 yum 来查找和安装软件（前者的经典分发包是Ubuntu，后者的经典分发包是Redhat），我还建议你试着从源码编译安装软件。</li>
</ul>
<h2 id="日常"><a class="header" href="#日常">日常</a></h2>
<ul>
<li>在 bash 里，使用 Ctrl-R 而不是上下光标键来查找历史命令。</li>
<li>在 bash里，使用 Ctrl-W 来删除最后一个单词，使用 Ctrl-U 来删除一行。请man bash后查找Readline Key Bindings一节来看看bash的默认热键，比如：Alt-. 把上一次命令的最后一个参数打出来，而Alt-* 则列出你可以输入的命令。</li>
<li>回到上一次的工作目录： cd –  （回到home是 cd ~）</li>
<li>使用 xargs。这是一个很强大的命令。你可以使用-L来限定有多少个命令，也可以用-P来指定并行的进程数。如果你不知道你的命令会变成什么样，你可以使用xargs echo来看看会是什么样。当然， -I{} 也很好用。示例：</li>
</ul>
<pre><code class="language-bash">find . -name \*.py | xargs grep some_function
 
cat hosts | xargs -I{} ssh root@{} hostname
</code></pre>
<ul>
<li>pstree -p 可以帮你显示进程树。（读过我的那篇《一个fork的面试题》的人应该都不陌生）</li>
<li>使用 pgrep 和 pkill 来找到或是kill 某个名字的进程。 (-f 选项很有用).</li>
<li>了解可以发给进程的信号。例如：要挂起一个进程，使用 kill -STOP [pid]. 使用 man 7 signal 来查看各种信号，使用kill -l 来查看数字和信号的对应表</li>
<li>使用 nohup 或  disown 如果你要让某个进程运行在后台。</li>
<li>使用netstat -lntp来看看有侦听在网络某端口的进程。当然，也可以使用 lsof。</li>
<li>在bash的脚本中，你可以使用 set -x 来debug输出。使用 set -e 来当有错误发生的时候abort执行。考虑使用 set -o pipefail 来限制错误。还可以使用trap来截获信号（如截获ctrl+c）。</li>
<li>在bash 脚本中，subshells (写在圆括号里的) 是一个很方便的方式来组合一些命令。一个常用的例子是临时地到另一个目录中，例如：</li>
</ul>
<pre><code class="language-bash"># do something in current dir
(cd /some/other/dir; other-command)
# continue in original dir
</code></pre>
<ul>
<li>在 bash 中，注意那里有很多的变量展开。如：检查一个变量是否存在: ${name:?error message}。如果一个bash的脚本需要一个参数，也许就是这样一个表达式 input_file=${1:?usage: $0 input_file}。一个计算表达式： i=$(( (i + 1) % 5 ))。一个序列： {1..10}。 截断一个字符串： ${var%suffix} 和 ${var#prefix}。 示例： if var=foo.pdf, then echo ${var%.pdf}.txt prints “foo.txt”.</li>
<li>通过 &lt;(some command) 可以把某命令当成一个文件。示例：比较一个本地文件和远程文件 /etc/hosts： diff /etc/hosts &lt;(ssh somehost cat /etc/hosts)</li>
<li>了解什么叫 “here documents” ，就是诸如 cat &lt;&lt;EOF 这样的东西。</li>
<li>在 bash中，使用重定向到标准输出和标准错误。如： some-command &gt;logfile 2&gt;&amp;1。另外，要确认某命令没有把某个打开了的文件句柄重定向给标准输入，最佳实践是加上 “&lt;/dev/null”，把/dev/null重定向到标准输入。</li>
<li>使用 man ascii 来查看 ASCII 表。</li>
<li>在远端的 ssh 会话里，使用 screen 或 dtach 来保存你的会话。（参看《28个Unix/Linux的命令行神器》）</li>
<li>要来debug Web，试试curl 和 curl -I 或是 wget 。我觉得debug Web的利器是firebug，curl和wget是用来抓网页的，呵呵。</li>
<li>把 HTML 转成文本： lynx -dump -stdin</li>
<li>如果你要处理XML，使用 xmlstarlet</li>
<li>对于 Amazon S3， s3cmd 是一个很方便的命令（还有点不成熟）</li>
<li>在 ssh中，知道怎么来使用ssh隧道。通过 -L or -D (还有-R) ，翻墙神器。</li>
<li>你还可以对你的ssh 做点优化。比如，.ssh/config 包含着一些配置：避免链接被丢弃，链接新的host时不需要确认，转发认证，以前使用压缩（如果你要使用scp传文件）：</li>
</ul>
<pre><code>TCPKeepAlive=yes
ServerAliveInterval=15
ServerAliveCountMax=6
StrictHostKeyChecking=no
Compression=yes
ForwardAgent=yes
</code></pre>
<p>如果你有输了个命令行，但是你改变注意了，但你又不想删除它，因为你要在历史命令中找到它，但你也不想执行它。那么，你可以按下 Alt-# ，于是这个命令关就被加了一个#字符，于是就被注释掉了。</p>
<h2 id="数据处理"><a class="header" href="#数据处理">数据处理</a></h2>
<ul>
<li>了解 sort 和 uniq 命令 (包括 uniq 的 -u 和 -d 选项).</li>
<li>了解用 cut, paste, 和 join 命令来操作文本文件。很多人忘了在cut前使用join。</li>
<li>如果你知道怎么用sort/uniq来做集合交集、并集、差集能很大地促进你的工作效率。假设有两个文本文件a和b已解被 uniq了，那么，用sort/uniq会是最快的方式，无论这两个文件有多大（sort不会被内存所限，你甚至可以使用-T选项，如果你的/tmp目录很小）</li>
</ul>
<pre><code class="language-bash">cat a b | sort | uniq &gt; c   # c is a union b 并集
 
cat a b | sort | uniq -d &gt; c   # c is a intersect b 交集
 
cat a b b | sort | uniq -u &gt; c   # c is set difference a - b 差集
</code></pre>
<ul>
<li>了解和字符集相关的命令行工具，包括排序和性能。很多的Linux安装程序都会设置LANG 或是其它和字符集相关的环境变量。这些东西可能会让一些命令（如：sort）的执行性能慢N多倍（注：就算是你用UTF-8编码文本文件，你也可以很安全地使用ASCII来对其排序）。如果你想Disable那个i18n 并使用传统的基于byte的排序方法，那就设置export LC_ALL=C （实际上，你可以把其放在 .bashrc）。如果这设置这个变量，你的sort命令很有可能会是错的。</li>
<li>了解 awk 和 sed，并用他们来做一些简单的数据修改操作。例如：求第三列的数字之和： awk ‘{ x += $3 } END { print x }’。这可能会比Python快3倍，并比Python的代码少三倍。</li>
<li>使用 shuf 来打乱一个文件中的行或是选择文件中一个随机的行。</li>
<li>了解sort命令的选项。了解key是什么（-t和-k）。具体说来，你可以使用-k1,1来对第一列排序，-k1来对全行排序。</li>
<li>Stable sort (sort -s) 会很有用。例如：如果你要想对两例排序，先是以第二列，然后再以第一列，那么你可以这样： sort -k1,1 | sort -s -k2,2</li>
<li>我们知道，在bash命令行下，Tab键是用来做目录文件自动完成的事的。但是如果你想输入一个Tab字符（比如：你想在sort -t选项后输入<tab>字符），你可以先按Ctrl-V，然后再按Tab键，就可以输入<tab>字符了。当然，你也可以使用$’\t’。</li>
<li>如果你想查看二进制文件，你可以使用hd命令（在CentOS下是hexdump命令），如果你想编译二进制文件，你可以使用bvi命令（http://bvi.sourceforge.net/ 墙）</li>
<li>另外，对于二进制文件，你可以使用strings（配合grep等）来查看二进制中的文本。</li>
<li>对于文本文件转码，你可以试一下 iconv。或是试试更强的 uconv 命令（这个命令支持更高级的Unicode编码）</li>
<li>如果你要分隔一个大文件，你可以使用split命令（split by size）和csplit命令（split by a pattern）。</li>
</ul>
<h2 id="系统调试"><a class="header" href="#系统调试">系统调试</a></h2>
<ul>
<li>如果你想知道磁盘、CPU、或网络状态，你可以使用 iostat, netstat, top (或更好的 htop), 还有 dstat 命令。你可以很快地知道你的系统发生了什么事。关于这方面的命令，还有iftop, iotop等（参看《28个Unix/Linux的命令行神器》）</li>
<li>要了解内存的状态，你可以使用free和vmstat命令。具体来说，你需要注意 “cached” 的值，这个值是Linux内核占用的内存。还有free的值。</li>
<li>Java 系统监控有一个小的技巧是，你可以使用kill -3 <pid> 发一个SIGQUIT的信号给JVM，可以把堆栈信息（包括垃圾回收的信息）dump到stderr/logs。</li>
<li>使用 mtr 会比使用 traceroute 要更容易定位一个网络问题。</li>
<li>如果你要找到哪个socket或进程在使用网络带宽，你可以使用 iftop 或 nethogs。</li>
<li>Apache的一个叫 ab 的工具是一个很有用的，用quick-and-dirty的方式来测试网站服务器的性能负载的工作。如果你需要更为复杂的测试，你可以试试 siege。</li>
<li>如果你要抓网络包的话，试试 wireshark 或 tshark。</li>
<li>了解 strace 和 ltrace。这两个命令可以让你查看进程的系统调用，这有助于你分析进程的hang在哪了，怎么crash和failed的。你还可以用其来做性能profile，使用 -c 选项，你可以使用-p选项来attach上任意一个进程。</li>
<li>了解用ldd命令来检查相关的动态链接库。注意：ldd的安全问题</li>
<li>使用gdb来调试一个正在运行的进程或分析core dump文件。参看我写的《GDB中应该知道的几个调试方法》</li>
<li>学会到 /proc 目录中查看信息。这是一个Linux内核运行时记录的整个操作系统的运行统计和信息，比如： /proc/cpuinfo, /proc/xxx/cwd, /proc/xxx/exe, /proc/xxx/fd/, /proc/xxx/smaps.</li>
<li>如果你调试某个东西为什么出错时，sar命令会有用。它可以让你看看 CPU, 内存, 网络, 等的统计信息。</li>
<li>使用 dmesg 来查看一些硬件或驱动程序的信息或问题。</li>
</ul>
<p>这里是描述和bash相关操作的章节。</p>
<h1 id="8个实用而有趣bash命令提示行"><a class="header" href="#8个实用而有趣bash命令提示行">8个实用而有趣Bash命令提示行</a></h1>
<p>很多人都对过命令行提示的重要性不屑一顾，甚至是一点都不关心。但是我却一点都不这么认为，一个好的命令行提示可以改变你使用命令的方式。为此，我在internet上找到一些非常实用，优秀，并有趣的bash的命令行提示。下面我将我最喜欢使用的一些命令行提示罗列如下。</p>
<p>注意  –  要使用下面这些提示，你可以拷贝粘贴这些以&quot;PS1″打头的内容到你的终端上，为了使你的改变永久生效，还要将这些内容粘贴到你使用用户的~/.bashrc文件中去。</p>
<h2 id="1-在成功执行的命令上增加一个笑脸符号"><a class="header" href="#1-在成功执行的命令上增加一个笑脸符号">1. 在成功执行的命令上增加一个笑脸符号</a></h2>
<p>这个命令提示行可能是这个命令行提示列表中最有趣的一个，但是它也依然有使用的价值。这个提示的想法是基于当你命令被成功执行，你将会得到一个笑脸作为你的命令行提示，一旦的命令执行失败，命令行提示将会换成一个哭脸。</p>
<p>代码：</p>
<pre><code class="language-bash">PS1=&quot;\`if [ \$? = 0 ]; then echo \[\e[33m\]^_^\[\e[0m\]; else echo \[\e[31m\]O_O\[\e[0m\]; fi\`[\u@\h:\w]\\$&quot;
</code></pre>
<h2 id="2更改失败命令的颜色"><a class="header" href="#2更改失败命令的颜色">2.更改失败命令的颜色</a></h2>
<p>下面这个命令行提示是我最喜欢的命令行之一。和上一个相似，这个命令行提示的颜色会在你最后一个命令运行失败后改变，而且这个命令行长路径会缩短输入命令的空间，这个命令提示还包含了bash 每个历史命令的命令号，以方便重新提取运行。</p>
<p>代码：</p>
<pre><code>PS1='\[\033[0;33m\][\!]\`if [[ \$? =&quot;0&quot; ]]; then echo&quot;\\[\\033[32m\\]&quot;; else echo&quot;\\[\\033[31m\\]&quot;; fi\`[\u.\h: \`if [[ `pwd|wc -c|tr -d &quot;&quot;` &gt; 18 ]]; then echo&quot;\\W&quot;; else echo&quot;\\w&quot;; fi\`]\$\[\033[0m\]&quot;; echo -ne &quot;\033]0;`hostname -s`:`pwd`\007'
</code></pre>
<h2 id="3-多行提示"><a class="header" href="#3-多行提示">3. 多行提示</a></h2>
<p>如果你是喜欢命令行提示中包含完整信息的那一类人，那么下边就有一个适合于你的命令行提示。这个命令行提示信息中包含日期/时间，全路径，用户，主机，活动终端，甚至包含文件数和占用空间等。</p>
<p>代码：</p>
<pre><code class="language-bash">PROMPT_COMMAND='PS1=&quot;\n\[\033[35m\]\$(/bin/date)\n\[\033[32m\]\w\n\[\033[1;31m\]\u@\h: \[\033[1;34m\]\$(/usr/bin/tty | /bin/sed -e ‘s:/dev/::’): \[\033[1;36m\]\$(/bin/ls -1 | /usr/bin/wc -l | /bin/sed ‘s: ::g’) files \[\033[1;33m\]\$(/bin/ls -lah | /bin/grep -m 1 total | /bin/sed ‘s/total //’)b\[\033[0m\] -&gt; \[\033[0m\]&quot;'
</code></pre>
<h2 id="4-多颜色提示"><a class="header" href="#4-多颜色提示">4. 多颜色提示</a></h2>
<p>这个命令行提示除了使用了不同颜色来区别不同信息外，它并没有很特别的地方。就像你看到的那样，它提供了时间，用户名，主机名，当前目录。相当少的信息，但是非常地实用。</p>
<p>代码：</p>
<pre><code class="language-bash">PS1=&quot;\[\033[35m\]\t\[\033[m\]-\[\033[36m\]\u\[\033[m\]@\[\033[32m\]\h:\[\033[33;1m\]\w\[\033[m\]\$&quot;
</code></pre>
<h2 id="5显示完整路径"><a class="header" href="#5显示完整路径">5.显示完整路径</a></h2>
<p>这是一个良好，简洁，最小的2行提示(加上顶上的空行)。在第一行你能得到一个全路径信息，在第二行是一个用户名。如果你对每个命令提示行的空行不爽的话，你只要移走第一个\n就OK了</p>
<p>代码：</p>
<pre><code class="language-bash">PS1=&quot;[\[\033[32m\]\w]\[\033[0m\]\n\[\033[1;36m\]\u\[\033[1;33m\]-&gt; \[\033[0m\]&quot;
</code></pre>
<h2 id="6-显示后台运行任务数"><a class="header" href="#6-显示后台运行任务数">6. 显示后台运行任务数</a></h2>
<p>这是另外的一个两行提示，但是这个两行提示具有更多的之前我们没有的信息。第一行是显示通常的user@host和全路径等信息。在第二行我们可以得到命令执行历史序号和一个后台运行任务个数信息。</p>
<p>代码：</p>
<pre><code class="language-bash">PS1='\[\e[1;32m\]\u@\H:\[\e[m\] \[\e[1;37m\]\w\[\e[m\]\n\[\e[1;33m\]hist:\! \[\e[0;33m\] \[\e[1;31m\]jobs:\j \$\[\e[m\]'
</code></pre>
<h2 id="7-显示路径信息"><a class="header" href="#7-显示路径信息">7. 显示路径信息</a></h2>
<p>这是一个非常眩的设计。我们可以从这个命令行提示信息的第一行中获取到用户/主机，运行任务数，和时间日期等信息。在第二行我们可以得到当前目录的文件数和他们占用的磁盘空间。</p>
<p>代码:</p>
<pre><code class="language-bash">PS1=&quot;\n\[\e[30;1m\]\[\016\]l\[\017\](\[\e[34;1m\]\u@\h\[\e[30;1m\])-(\[\e[34;1m\]\j\[\e[30;1m\])-(\[\e[34;1m\]\@ \d\[\e[30;1m\])-&gt;\[\e[30;1m\]\n\[\016\]m\[\017\]-(\[\[\e[32;1m\]\w\[\e[30;1m\])-(\[\e[32;1m\]\$(/bin/ls -1 | /usr/bin/wc -l | /bin/sed ‘s: ::g’) files, \$(/bin/ls -lah | /bin/grep -m 1 total | /bin/sed ‘s/total //’)b\[\e[30;1m\])–&gt; \[\e[0m\]&quot;
</code></pre>
<h2 id="8-my-prompt"><a class="header" href="#8-my-prompt">8. My Prompt</a></h2>
<p>最后这个命令提示行是我个人最喜欢的使用的命令提示行。它是#7的一个修改，这个命令提示行只包含我最希望知道的信息，因此节省了它的占用空间。我偏爱两行风格，因为这样不仅可以让我看到全路径信息，而且不影响我命令输入的可视空间。</p>
<p>代码:</p>
<pre><code class="language-bash">PS1=&quot;\n\[\e[32;1m\](\[\e[37;1m\]\u\[\e[32;1m\])-(\[\e[37;1m\]jobs:\j\[\e[32;1m\])-(\[\e[37;1m\]\w\[\e[32;1m\])\n(\[\[\e[37;1m\]! \!\[\e[32;1m\])-&gt; \[\e[0m\]&quot;
</code></pre>
<h1 id="jobs-backgrounds-kills-and-interruptions"><a class="header" href="#jobs-backgrounds-kills-and-interruptions">Jobs: backgrounds, Kills And Interruptions</a></h1>
<h2 id="后台运行"><a class="header" href="#后台运行">后台运行</a></h2>
<p>在执行的命令后边带上<code>&amp;</code>符号，可以让命令在后台执行。</p>
<p>如:</p>
<p><code>$ mycommand &amp;</code></p>
<p>使用<code>%jobNum</code>可以引用后台运行的任务, jobNum可以通过<code>jobs</code>命令获取。</p>
<h2 id="前后台切换"><a class="header" href="#前后台切换">前后台切换</a></h2>
<p>使用<code>fg [%jobNum]</code>可以把后台任务调到前台来执行，如果不指定jobNum,则把最近的任务调到前台，否则把指定的job调到前台来。</p>
<p>使用<code>ctrl z</code>快捷键可以把当前执行的任务暂停，并放到后台去，如果想在后台运行，可以使用<code>bg [%jobNum]</code>来运行。</p>
<p>通过一个小例子来演示一下任务的前后台切换。</p>
<pre><code class="language-sh">$ sleep 100 &amp;
[1] 13133
$ sleep 100 &amp;
[2] 13134
$ jobs
[1]-  Running                 sleep 100 &amp;
[2]+  Running                 sleep 100 &amp;
</code></pre>
<p>通过<code>jobs</code>可以看到在后台运行了两个sleep命令。下面我们把[2]号任务调到前台来。</p>
<pre><code class="language-sh">$ fg %2
sleep 100

</code></pre>
<p>现在我们把第二个<code>sleep 100</code>命令调到前台执行了，如果我们想再把它放入后台执行，只能通过<code>ctrl z</code>的方式，先把它暂停，拿到命令执行权，然后通过<code>bg %2</code>的方式，
让它在后台执行。</p>
<pre><code class="language-sh">$ fg %2
sleep 100
^Z
[2]+ Stopped
$ bg %2
[2]+ sleep 100 &amp;
</code></pre>
<p>bg和fg一样，如果不跟argument的话，默认操作的是最近的任务。</p>
<h2 id="kill"><a class="header" href="#kill">kill</a></h2>
<p>如果想杀掉后台任务，可以使用<code>kill %jobNum</code>的方式，简单快捷的杀死job。</p>
<h2 id="总结"><a class="header" href="#总结">总结</a></h2>
<p>Jobs管理操作表</p>
<table><thead><tr><th>Background Jobs</th><th>Execution</th></tr></thead><tbody>
<tr><td>%jobnum</td><td>引用一个job,jobnum可以通过jobs查看获得</td></tr>
<tr><td>%</td><td>引用最近的一个job</td></tr>
<tr><td>&amp;</td><td>在后台执行命令</td></tr>
<tr><td>fg %jobnum</td><td>把后台任务调到前台或者激活一个暂停的任务</td></tr>
<tr><td>fg</td><td>引用最近的一个job</td></tr>
<tr><td>CTRL-z</td><td>暂停目前正在执行的程序</td></tr>
<tr><td>kill %jobnum</td><td>杀死指定的job</td></tr>
</tbody></table>
<h1 id="变量"><a class="header" href="#变量">变量</a></h1>
<h2 id="定义和执行变量setunset"><a class="header" href="#定义和执行变量setunset">定义和执行变量:=,$,set,unset</a></h2>
<h2 id="double-quotes-single-quotes-and-backslash"><a class="header" href="#double-quotes-single-quotes-and-backslash">double quotes, single quotes and backslash</a></h2>
<h1 id="表达式"><a class="header" href="#表达式">表达式</a></h1>
<h2 id="back-quotes"><a class="header" href="#back-quotes">back quotes</a></h2>
<h1 id="控制语句"><a class="header" href="#控制语句">控制语句</a></h1>
<h2 id="test-operations"><a class="header" href="#test-operations">test operations</a></h2>
<h2 id="条件控制"><a class="header" href="#条件控制">条件控制</a></h2>
<h2 id="循环控制"><a class="header" href="#循环控制">循环控制</a></h2>
<h1 id="环境变量和子shell"><a class="header" href="#环境变量和子shell">环境变量和子shell</a></h1>
<h1 id="switch控制"><a class="header" href="#switch控制">switch控制</a></h1>
<h1 id="函数"><a class="header" href="#函数">函数</a></h1>
<p>在编程中，argument指的是函数的实参，parameter指的是函数的形参。</p>
<p>在shell中命令后面跟的所有参数都可以称为argument，如<code>find . -type f</code>。option是带-或者--符号的argument, option能影响命令的执行行为。option后面跟的参数可以称为parameter, 如这里的f就是-type的参数。</p>
<h1 id="如何调试bash脚本"><a class="header" href="#如何调试bash脚本">如何调试bash脚本</a></h1>
<p>Bash 是Linux操作系统的默认Shell脚本。Shell是用来处理操作系统和用户交互的一个程序。Shell的脚本可以帮助用户自动化地和操作系统进行交互。你也可以理解为一种脚本式的编程。即然有编程，那么，程序的编译器，解释器，调试器就必不可少了，Bash也一样，但在调试方面可能会有一些和编程语言不一样的东西和技术，所以，下面这篇文章主要是说明调试bash脚本的各种技术。</p>
<h2 id="跟踪脚本的执行"><a class="header" href="#跟踪脚本的执行">跟踪脚本的执行</a></h2>
<p>你可以让bash打印出你脚本执行的过程中的所有语句。这很简单，只需要使用bash的-x选项就可以做到，下面让我们来看一下。</p>
<p>下面的这段脚本，先是输出一个问候语句，然后输出当前的时间：</p>
<pre><code class="language-bash">#!/bin/bash
echo &quot;Hello $USER,&quot;
echo &quot;Today is $(date +'%Y-%m-%d')&quot;
</code></pre>
<p>下面让我们使用-x选项来运行这段脚本：</p>
<pre><code>$ bash -x example_script.sh
+ echo 'Hello chenhao,'
Hello chenhao,
++ date +%Y-%m-%d
+ echo 'Today is 2009-08-31'
Today is 2009-08-31
</code></pre>
<p>这时，我们可以看到，bash在运行前打印出了每一行命令。而且每行前面的+号表明了嵌套。这样的输出可以让你看到命令执行的顺序并可以让你知道整个脚本的行为。
在跟踪里输出行号</p>
<p>在一个很大的脚本中，你会看到很多很多的执行跟踪的输出，阅读起来非常费劲，所以，你可以在每一行前加上文件的行号，这会非常有用。要做到这样，你只需要设置下面的环境变量：</p>
<pre><code>export PS4='+${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]}: '
</code></pre>
<p>让我们看看设置上了PS4这个环境变量后会是什么样的输出。</p>
<pre><code>$ bash -x example_script.sh
+example_script.sh:2:: echo 'Hello chenhao,'
Hello chenhao,
++example_script.sh:3:: date +%Y-%m-%d
+example_script.sh:3:: echo 'Today is 2009-08-31'
Today is 2009-08-31
</code></pre>
<h2 id="调试部份的脚本"><a class="header" href="#调试部份的脚本">调试部份的脚本</a></h2>
<p>有些时候，你并不想调试整个脚本，你只要调试其中的一部份，那么，你可以在你想要调试的脚本之前，调用“set -x”，结束的时候调用“set +x”就可以了。如下面的脚本所示：</p>
<pre><code>#!/bin/bash
echo &quot;Hello $USER,&quot;
set -x
echo &quot;Today is $(date %Y-%m-%d)&quot;
set +x
</code></pre>
<p>让我们看看运行起来是啥样？</p>
<pre><code>$ ./example_script.sh
Hello chenhao,
++example_script.sh:4:: date +%Y-%m-%d
+example_script.sh:4:: echo 'Today is 2009-08-31'
Today is 2009-08-31
+example_script.sh:5:: set +x
</code></pre>
<p>注意：我们在运行脚本的时候，不需要使用bash -x了。</p>
<h2 id="日志输出"><a class="header" href="#日志输出">日志输出</a></h2>
<p>跟踪日志有时候太多了，多得都受不了，而且，输出的内容很难阅读。一般来说，我们很多时候只关心于条件表达式，变量值，或是函数调用，或是循环等。。在这种情况下，log一些感兴趣的特定的信息，可能会更好。</p>
<p>使用log前，我们先写一个函数：</p>
<pre><code>_log() {
    if [ &quot;$_DEBUG&quot; == &quot;true&quot; ]; then
        echo 1&gt;&amp;2 &quot;$@&quot;
    fi
}
</code></pre>
<p>于是，你就可以在你的脚本中如下使用：</p>
<pre><code>_log &quot;Copying files...&quot;
cp src/* dst/
</code></pre>
<p>我们可以看到，上面那个_log函数，需要检查一个_DEBUG 变量，只有这个变量是真，才会真正开发输出日志。这样，你就只需要控制这个开关，而不需要删除你的debug信息。</p>
<pre><code>$ _DEBUG=true ./example_script.sh
</code></pre>
<h2 id="使用bash专用调试器"><a class="header" href="#使用bash专用调试器">使用Bash专用调试器</a></h2>
<p>如果你在写一个相当复杂的脚本，并且，你需要一个完整的像调试别的语言一样的调试器，那么你可以试着用用这个开源软件—— bashdb， 一个Bash的专用调试器。这个调试器很强大，你想得到的功能，他都有，比如，设置断点，单步跟踪，跳出函数，等等。它的用户接口很想GDB，这是他的文档 。</p>
<p>cheat_sheet</p>
<ul>
<li>
<p><sh/> $# 传递到脚本的参数个数</p>
</li>
<li>
<p><sh/> $* 以一个单字符串显示所有向脚本传递的参数 以&quot;$1 $2 … $n&quot;的形式输出所有参数</p>
</li>
<li>
<p><sh/> $$ 脚本运行的当前进程ID号</p>
</li>
<li>
<p><sh/> $! 后台运行的最后一个进程的ID号</p>
</li>
<li>
<p><sh/> $@ 与$*相同，但是使用时加引号，并在引号中返回每个参数。以&quot;$1&quot; &quot;$2&quot; … &quot;$n&quot; 的形式输出所有参数。</p>
</li>
<li>
<p><sh/> $- 显示Shell使用的当前选项，与set命令功能相同。</p>
</li>
<li>
<p><sh/> $? 显示最后命令的退出状态。0表示没有错误，其他任何值表明有错误。</p>
</li>
<li>
<p><sh/> array array_name=(value1 value2 ... valuen) Shell 数组用括号来表示，元素用&quot;空格&quot;符号分割开</p>
</li>
<li>
<p><sh/> array_name[0]=value0 array_name[1]=value1 array_name[2]=value2</p>
</li>
<li>
<p><sh/> 读取数组元素值的一般格式是： ${array_name[index]}</p>
</li>
<li>
<p><sh/> 使用@ 或 * 可以获取数组中的所有元素 ${my_array[*]} ${my_array[@]}</p>
</li>
<li>
<p><sh/> 获取数组长度的方法与获取字符串长度的方法相同  ${#my_array[*]} ${#my_array[@]}</p>
</li>
<li>
<p><sh/> 删除变量 unset variable_name</p>
</li>
<li>
<p><sh/> 获取字符串长度 string=&quot;abcd&quot;; echo ${#string}</p>
</li>
<li>
<p><sh/> 提取子字符串  string=&quot;runoob is a great site&quot;; echo ${string:1:4}  输出 unoo</p>
</li>
<li>
<p><sh/> 关系运算符 等于 [ $a -eq $b ]</p>
</li>
<li>
<p><sh/> 关系运算符 不等于 [ $a -ne $b ] 返回 true。</p>
</li>
<li>
<p><sh/> [ $a -gt $b ]</p>
</li>
<li>
<p><sh/> [ $a -lt $b ] </p>
</li>
<li>
<p><sh/> [ $a -ge $b ]</p>
</li>
<li>
<p><sh/> [ $a -le $b ]</p>
</li>
<li>
<p><sh/> [ ! false ] </p>
</li>
<li>
<p><sh/> [ $a -lt 20 -o $b -gt 100 ]</p>
</li>
<li>
<p><sh/> [ $a -lt 20 -a $b -gt 100 ] </p>
</li>
<li>
<p><sh/> [[ $a -lt 100 &amp;&amp; $b -gt 100 ]]</p>
</li>
<li>
<p><sh/> [[ $a -lt 100 || $b -gt 100 ]] </p>
</li>
<li>
<p><sh/> [[]] 是 [] 的增强版</p>
</li>
<li>
<p><sh/> [ $a = $b ] 返回 false</p>
</li>
<li>
<p><sh/> [ $a != $b ] 返回 true</p>
</li>
<li>
<p><sh/> [ -z $a ] 检测字符串长度是否为0，为0返回 true。</p>
</li>
<li>
<p><sh/> [ -n &quot;$a&quot; ] 检测字符串长度是否不为 0，不为 0 返回 true</p>
</li>
<li>
<p><sh/> [ $a ] 检测字符串是否为空，不为空返回 true。</p>
</li>
<li>
<p><sh/> [ -b $file ] 检测文件是否是块设备文件，如果是，则返回 true。</p>
</li>
<li>
<p><sh/> [ -c $file ] 检测文件是否是字符设备文件，如果是，则返回 true</p>
</li>
<li>
<p><sh/> [ -d $file ] 检测文件是否是目录，如果是，则返回 true</p>
</li>
<li>
<p><sh/> [ -f $file ] 检测文件是否是普通文件（既不是目录，也不是设备文件），如果是，则返回 true</p>
</li>
<li>
<p><sh/> [ -g $file ] 检测文件是否设置了 SGID 位，如果是，则返回 true</p>
</li>
<li>
<p><sh/> [ -k $file ] 检测文件是否设置了粘着位(Sticky Bit)，如果是，则返回 true</p>
</li>
<li>
<p><sh/> [ -p $file ] 检测文件是否是有名管道，如果是，则返回 true</p>
</li>
<li>
<p><sh/> [ -u $file ] 检测文件是否设置了 SUID 位，如果是，则返回 true。</p>
</li>
<li>
<p><sh/> [ -r $file ] 检测文件是否可读，如果是，则返回 true。</p>
</li>
<li>
<p><sh/> [ -w $file ] 检测文件是否可写，如果是，则返回 true。</p>
</li>
<li>
<p><sh/> [ -x $file ] 检测文件是否可执行，如果是，则返回 true。</p>
</li>
<li>
<p><sh/> [ -s $file ] 检测文件是否为空（文件大小是否大于0），不为空返回 true。</p>
</li>
<li>
<p><sh/> [ -e $file ] 检测文件（包括目录）是否存在，如果是，则返回 true。</p>
</li>
<li>
<p><sh/> printf  format-string  [arguments...] 郭靖 男 66.1234 printf 由 POSIX 标准所定义，因此使用 printf 的脚本比使用 echo 移植性好。</p>
</li>
<li>
<p><sh/> if [ $(ps -ef | grep -c &quot;ssh&quot;) -gt 1 ]; then echo &quot;true&quot;; fi</p>
</li>
<li>
<p><sh/> for var in item1 item2 ... itemN; do done</p>
</li>
<li>
<p><sh/> for((i=1;i&lt;=5;i++));do ... done;</p>
</li>
<li>
<p><sh/> for f in $(ls); do done</p>
</li>
<li>
<p><sh/> while(( $int&lt;=5 )); do done</p>
</li>
<li>
<p><sh/> case ... esac</p>
<p>case $aNum in</p>
<ol>
<li>echo '你选择了 1'
;;</li>
<li>echo '你选择了 2'
;;</li>
<li>echo '你选择了 3'
;;</li>
<li>echo '你选择了 4'
;;
*)  echo '你没有输入 1 到 4 之间的数字'
;;
esac</li>
</ol>
</li>
<li>
<p><sh/> function</p>
<p>[ function ] funname [()]
{
action;
[return int;]
}</p>
</li>
</ul>
<p>Linux常用命令的介绍和使用。</p>
<ul>
<li>
<p>awk历史</p>
<ul>
<li>起源</li>
<li>发展</li>
<li>现在</li>
</ul>
</li>
<li>
<p>awk 语法</p>
<ul>
<li>创建数组</li>
<li>删除数组元素</li>
<li>多维数组</li>
</ul>
</li>
<li>
<p>awk 数组</p>
</li>
<li>
<p>awk 内置变量</p>
</li>
</ul>
<h1 id="awk历史"><a class="header" href="#awk历史">awk历史</a></h1>
<p>awk 名称的由来是由它的原始设计者的姓氏之第一个字母而命名Alfred V. <font color="red">A</font>ho, Peter J. <font color="red">W</font>einberger和Brian W. <font color="red">K</font>ernighan。</p>
<p>awk最初在1977年完成。一个新版本的awk在1985年被发表，它的功能比旧版本增强不少。</p>
<p>gawk 是GNU所做的,gawk 最初在1986年完成，之后不断地被改进、更新。gawk 包含 awk 的所有功能。目前使用的大部分都是gawk。</p>
<h1 id="awk语法"><a class="header" href="#awk语法">awk语法</a></h1>
<p>awk分为两种命令格式，一种是从文件中读取awk 程序体，一种直接写在命令行上。如下所示:</p>
<ul>
<li><code>awk [options] program-body [text-file]</code></li>
<li><code>awk [options] -f program-file [text-file]</code></li>
</ul>
<p>awk的语法如下:</p>
<pre><code># 这是注释
BEGIN {
    statement
    statement
}
# 每行的匹配模式
match_pattern
# 对匹配行的操作
{
    statement
    statement
}

END {
    statement
    statement
}
</code></pre>
<p>上面是awk程序多行的写法，可以写到一个文件中，awk也可以写成一行，如下所示:</p>
<pre><code>'BEGIN {statement;statement;} match_pattern{statement;statement;} END{statement;statement;}'
</code></pre>
<p>awk程序分为了三个模块，BEGIN, match_pattern, END。BEGIN部分会在正式处理文本行之前执行，可以设置一些变量或者打印一下表头; match_pattern是对输入行的过滤，awk对输入的文本一行一行的迭代式的使用match_pattern部分处理，符合match_pattern规则的，就会执行对应的语句; END是文本处理完毕之后执行的。</p>
<p>三个部分都是可选的，只有其中的一个也行。我们重点说一下match_pattern部分的具体语法和使用，毕竟它才是处理文本的关键点。</p>
<p>match_pattern 支持表达式，支持逻辑运算，支持正则表达式。以/etc/passwd文件的处理为例，介绍一下这三种表达式的使用.</p>
<pre><code># 没有指定match_pattern，表示处理每一行
awk -F':' '{print $1}' /etc/passwd
# 匹配第三列值是0的行
awk -F':' '$3==0{print $1}' /etc/passwd
awk -F':' '$3==0 || $1 == &quot;root&quot;{print $1}' /etc/passwd
# 正则表达式
awk -F':' '/s*/{print $1}' /etc/passwd
# 正则表示式与逻辑运算
awk -F':' '$3==0 || /s*/{print $1}' /etc/passwd
</code></pre>
<p>逻辑运算支持! &amp;&amp; ||三种，和C语言一样。</p>
<p>awk程序中也可以使用变量，变量不用声明，可以直接使用。如计算一列的值，文本内容如下</p>
<pre><code>56K     /var/log/apt
0       /var/log/dist-upgrade
0       /var/log/journal
28K     /var/log/landscape
0       /var/log/lxd
16K     /var/log/nginx
0       /var/log/sysstat
0       /var/log/unattended-upgrades
5.5M    /var/log
</code></pre>
<p>计算第一列的值，当然简化一下问题，我们只计算单位是K的</p>
<pre><code>du -h /var/log | awk -F' ' '/\d?K/{print $1}' | awk -F'K' '{sum=sum+$1} END{print &quot;SUM=&quot;,sum}'
</code></pre>
<p>awk的语法和python很像，如果你熟悉python的话。当然你也认为它像C,如果你只熟悉C的话。</p>
<p>awk的输出有两种格式，print和printf。</p>
<p>这里只介绍awk程序的基本语法和程序结构，它还有很多语法点和内置函数以及内置的变量，如for while if 数组 等等它都是支持的，这里不在一一介绍。</p>
<p>我还提供了一个awk的<a href="cmd/./cheat_sheet/awk.html">cheat sheet</a>。</p>
<h1 id="awk-数组"><a class="header" href="#awk-数组">awk 数组</a></h1>
<p>AWK 可以使用关联数组这种数据结构，索引可以是数字或字符串。AWK关联数 组也不需要提前声明其大小，因为它在运行时可以自动的增大或减小。</p>
<pre><code>array_name[index]=value
</code></pre>
<ul>
<li>array_name：数组的名称</li>
<li>index：数组索引</li>
<li>value：数组中元素所赋予的值</li>
</ul>
<h2 id="创建数组"><a class="header" href="#创建数组">创建数组</a></h2>
<pre><code>$ awk 'BEGIN {
sites[&quot;runoob&quot;]=&quot;www.runoob.com&quot;;
sites[&quot;google&quot;]=&quot;www.google.com&quot;
print sites[&quot;runoob&quot;] &quot;\n&quot; sites[&quot;google&quot;]
}'
</code></pre>
<h2 id="删除数组元素"><a class="header" href="#删除数组元素">删除数组元素</a></h2>
<pre><code>delete array_name[index]
</code></pre>
<h2 id="多维数组"><a class="header" href="#多维数组">多维数组</a></h2>
<p>AWK 本身不支持多维数组，不过我们可以很容易地使用一维数组模拟实现多维数组。如下示例为一个 3x3 的二维数组：</p>
<pre><code>100 200 300
400 500 600
700 800 900
</code></pre>
<p>以上实例中，array[0][0] 存储 100，array[0][1] 存储 200 ，依次类推。为了在 array[0][0] 处存储 100, 我们可以使用如下语法： array[&quot;0,0&quot;] = 100。</p>
<p>我们使用了 0,0 作为索引，但是这并不是两个索引值。事实上，它是一个字符串索引 0,0。</p>
<h1 id="awk-内置变量"><a class="header" href="#awk-内置变量">awk 内置变量</a></h1>
<table><thead><tr><th>变量名</th><th>属性</th></tr></thead><tbody>
<tr><td>$0</td><td>当前记录变量，默认一行是一条记录</td></tr>
<tr><td>$1~$n</td><td>当前记录中的第n个字段变量</td></tr>
<tr><td>FS</td><td>字段分隔符，默认空格</td></tr>
<tr><td>RS</td><td>记录分隔符，默认换行符</td></tr>
<tr><td>NF</td><td>当前记录一共有多少字段，也就是有多少列</td></tr>
<tr><td>NR</td><td>当前记录的编号，默认从1开始，相当于行号</td></tr>
<tr><td>OFS</td><td>输出字段分隔符，默认空格</td></tr>
<tr><td>ORS</td><td>输出记录分隔符，默认换行符</td></tr>
</tbody></table>
<h1 id="awk-内置函数"><a class="header" href="#awk-内置函数">awk 内置函数</a></h1>
<table><thead><tr><th>函数</th><th>说明</th></tr></thead><tbody>
<tr><td>length([string])</td><td>获取字符串长度</td></tr>
<tr><td>sprintf(formant,...)</td><td>格式话字符串，并返回</td></tr>
</tbody></table>
<p>nm命令是names的缩写， GNU nm lists the symbols from object files objfile....  If no object files are listed as arguments, nm assumes the file a.out.</p>
<p>可以使用nm列出obj对象中的符号，配合grep查找到符号的定义处。</p>
<ul>
<li>nm -A 输出的符号名前有文件名</li>
</ul>
<p>下面是一个小例子</p>
<pre><code># nm  -A a.o
a.o:                 U _GLOBAL_OFFSET_TABLE_
a.o:0000000000000000 T print
a.o:                 U puts
</code></pre>
<p>U表示undefined,是未定义的符号，T表示text即代码段的符号，是定义的符号。</p>
<h1 id="strace_ltrace"><a class="header" href="#strace_ltrace">strace_ltrace</a></h1>
<h2 id="功能"><a class="header" href="#功能">功能</a></h2>
<p>strace是一个跟踪系统调用的工具。用于分析内核态的函数使用情况。</p>
<p>ltrace是一个跟踪用户态调用的工具。</p>
<ul>
<li></li>
</ul>
<p>cheat_sheet</p>
<ul>
<li>awk '$1~/hello/{print &quot;ok&quot;}}' 正则表达式匹配第一列中含有hello的行</li>
<li>awk 'BEGIN{a=&quot;b&quot;;print a++,++a}' 所有用作算术运算符进行操作，操作数自动转为数值，所有非数值都变为0</li>
<li>awk 'BEGIN{a=&quot;b&quot;;print a==&quot;b&quot;?&quot;ok&quot;:&quot;err&quot;}'  三目运算符</li>
<li>awk for ( x=1;x&lt;=4;x++ ) { print &quot;iteration&quot;, x} for循环</li>
<li>awk break continue 和C语言一样</li>
<li>awk x=1;while(1) { print &quot;iteration&quot;, x; if ( x==10 ) { break } x++ } while和break的使用</li>
<li>awk cities[1]=&quot;beijing&quot; cities[&quot;three&quot;]=&quot;guangzhou&quot; for( c in cities) { print cities[c] } awk数组索引都是字符串，就算是数字也会转成字符串</li>
<li>awk '{arr[$4]=$0} END{for(i in arr){ print i,arr[i] | &quot;sort -r -n -k1&quot; }}' watch.log  按照第4个字段，按照数字类型进行排序</li>
</ul>
<ul>
<li>tcpdump -w - | tee somefile | tcpdump -r -  -w - 让tcpdump把二进制数据写到标准输出流，tee把数据写到文件并输出到标准输出流,-r -从标准输入流读取二进制数据</li>
<li>tcpdump -D 列出所有可以捕获的设备‘</li>
<li>tcpdump -i interface  捕获指定的设备</li>
<li>tcpdump -w fileName   把捕获的数据写道文件</li>
<li>tcpdump -C  file_size -w fileName 指定单个文件的大小，单位是1000000B，不是1024*1024B,当写满一个文件后会重新打开另一个文件</li>
<li>tcpdump -c count      指定捕获的包数据量，达到数量后就会停止捕获</li>
<li>tcpdump [dst|src] [host xxx] [port xxx] 指定捕获的目标或者源ip是xxx,port是xxx的包</li>
<li>tcpdump -S            显示绝对seq号，而不是相对seq号，默认显示的相对seq号(wireshark也是,edit-&gt;preferences-&gt;protocal-&gt;tcp-&gt;relative sequence numbers)</li>
<li>tcpdump -i lo 'tcp[13] &amp; 4 = 4' 捕获RST报文段，tcp[13]表示的是tcp协议头中偏移量为13的字节</li>
</ul>
<p>这一章介绍Linux文件系统相关的知识。</p>
<p>子模块命名上使用_符号替代/符号，比如_proc_[pid]_status表示的就是文件系统中/proc/[pid]/status文件。</p>
<ul>
<li>什么是文件系统</li>
<li>分区和文件系统</li>
<li>不同文件系统的区别是啥</li>
<li>文件系统主要模块</li>
</ul>
<h1 id="什么是文件系统"><a class="header" href="#什么是文件系统">什么是文件系统</a></h1>
<p>在说什么是文件系统之前，先说一下什么是系统。不同领域，不同环境下系统一词的含义都不太一样，而且系统又有名词和形容词两种词性。</p>
<p>首先作为名词时，主流含义如下:</p>
<ol>
<li>系统是相互联系相互作用的诸元素的综合体。</li>
<li>由一些相互联系、相互制约的若干组成部分结合而成的、具有特定功能的一个有机整体。</li>
</ol>
<p>虽然定义1和定义2 看着很像，但是其定义的目的完全不同。定义1适用于客观存在的一个内部平衡的自然体。如天体运行系统，生物圈系统等等。
定义2是从人的角度定义的，强调的是一个可以对人提供特定功能，内部相互作用的整体。多用来指代人造系统，比如预警系统等。</p>
<p>作为形容词时，含义是有条例的，层次清晰的，如我系统整理了操作系统知识。</p>
<p>第一个<strong>系统</strong>就是作为形容词出现的，第二个<strong>系统</strong>作为一个名词，但是和其通用解释不太一样。这就得说一下<strong>系统</strong>在计算机领域的含义了。</p>
<p>系统在计算机中，通常表示的是能调度管理特定的资源，提供接口，以使外部使用系统管理的资源的一个程序。</p>
<p>如操作系统就是用来管理cpu,内存，磁盘，网络，进程，其他外设，以系统中断的方式，为外部提供操作这些资源的功能。同理，文件系统就是管理文件数据在磁盘上存放和读取功能的，按照协议被操作系统调用。</p>
<p>文件系统中主要的数据结构有 superblock, inode, data block, directory bolock和indirection block.</p>
<p>Most UNIX filesystem types have a similar general structure, although the exact details vary quite a bit. The
central concepts are superblock, inode, data block, directory block, and indirection block. The superblock
contains information about the filesystem as a whole, such as its size (the exact information here depends on
the filesystem). An inode contains all information about a file, except its name. The name is stored in the
directory, together with the number of the inode. A directory entry consists of a filename and the number of
the inode which represents the file. The inode contains the numbers of several data blocks, which are used to
store the data in the file. There is space only for a few data block numbers in the inode, however, and if more
are needed, more space for pointers to the data blocks is allocated dynamically. These dynamically allocated
blocks are indirect blocks; the name indicates that in order to find the data block, one has to find its number in
the indirect block first.</p>
<h1 id="不同文件系统的区别是啥"><a class="header" href="#不同文件系统的区别是啥">不同文件系统的区别是啥</a></h1>
<h1 id="文件系统主要模块"><a class="header" href="#文件系统主要模块">文件系统主要模块</a></h1>
<p>_proc</p>
<ul>
<li>关于/proc/$pid/fd socket:[number]</li>
<li>linux /proc/net/tcp 文件分析</li>
</ul>
<h1 id="关于procpidfd-socketnumber"><a class="header" href="#关于procpidfd-socketnumber">关于/proc/$pid/fd socket:[number]</a></h1>
<p>众所周知，在相应进程的/proc/$pid/fd 目录下存放了此进程所有打开的fd。当然有些可能不是本进程自己打开的，如通过fork()从父进程继承而来的。本文着着重讲述socket有关的内容。当我们在fd目录下使用 ls -l 命令查看时，会看到诸如下面的内容： </p>
<pre><code>lrwx------ 1 root root 64 Nov 21 09:44 133 -&gt; /dev/sda1
lrwx------ 1 root root 64 Nov 21 09:44 134 -&gt; /dev/sdb1
lrwx------ 1 root root 64 Nov 21 09:44 136 -&gt; /dev/sdb1
lrwx------ 1 root root 64 Nov 21 09:44 137 -&gt; socket:[22460]
lrwx------ 1 root root 64 Nov 21 09:44 138 -&gt; socket:[7326842]
lrwx------ 1 root root 64 Nov 21 09:44 139 -&gt; socket:[7341066]
</code></pre>
<p>那么这个socket:后面的一串数字是什么呢？其实是该socket的inode号。</p>
<p>那么，知道了某个进程打开的socket的inode号后，我们可以做什么呢？这就涉及到/proc/net/tcp(udp对应/proc/net/udp)文件了，其中也列出了相应socket的inode号通过比对此字段，我们能在/proc/net/tcp下获得此套接口的其他信息，如对应的&lt;本地地址：端口号，远端地址：端口号&gt;对，窗口大小，状态等信息。</p>
<h1 id="linux-procnettcp-文件分析"><a class="header" href="#linux-procnettcp-文件分析">linux /proc/net/tcp 文件分析</a></h1>
<p>https://github.com/torvalds/linux/blob/v4.10/Documentation/networking/proc_net_tcp.txt</p>
<p>在linux中执行cat /proc/net/tcp，输出结果：</p>
<pre><code>sl  local_address rem_address   st tx_queue rx_queue tr tm-&gt;when retrnsmt   uid  timeout inode                                                     
0: 00000000:1A0A 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 195169 1 ffff9d0e6de83480 100 0 0 10 0                    
1: 3500007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000   101        0 20428 1 ffff9d0e7c3a2bc0 100 0 0 10 0                     
2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 25992 1 ffff9d0e7c3a4ec0 100 0 0 10 0                     
3: 02FEA8C0:0016 01FEA8C0:FEE0 01 00000000:00000000 02:000221C7 00000000     0        0 179483 2 ffff9d0e7c3a3480 20 4 0 10 20                    
4: 0100007F:1A0A 0100007F:DEB4 08 00000000:00000000 00:00000000 00000000     0        0 195170 1 ffff9d0e7c3a6040 20 4 28 10 -1                   
5: 0100007F:1A0A 0100007F:DEB6 08 00000000:00000000 00:00000000 00000000     0        0 195513 1 ffff9d0e7c3a2300 20 4 0 10 -1                    
6: 02FEA8C0:0016 01FEA8C0:CF73 01 00000024:00000000 01:00000018 00000000     0        0 177469 4 ffff9d0e7c3a1180 24 4 31 10 -1                   
7: 0100007F:1A0A 0100007F:DEB8 08 00000000:00000000 00:00000000 00000000     0        0 200679 1 ffff9d0e7c3a4600 21 4 28 10 -1                   
</code></pre>
<p>对其中每个字段的解释如下（由于长度原因，分为3个部分）：</p>
<pre><code>46: 010310AC:9C4C 030310AC:1770 01 
|      |      |      |      |   |--&gt; connection state
|      |      |      |      |------&gt; remote TCP port number
|      |      |      |-------------&gt; remote IPv4 address
|      |      |--------------------&gt; local TCP port number
|      |---------------------------&gt; local IPv4 address
|----------------------------------&gt; number of entry
</code></pre>
<p>connection state(套接字状态)，不同的数值代表不同的状态，参照如下：</p>
<pre><code>TCP_ESTABLISHED:1   TCP_SYN_SENT:2
TCP_SYN_RECV:3      TCP_FIN_WAIT1:4
TCP_FIN_WAIT2:5     TCP_TIME_WAIT:6
TCP_CLOSE:7         TCP_CLOSE_WAIT:8
TCP_LAST_ACL:9      TCP_LISTEN:10
TCP_CLOSING:11


00000150:00000000 01:00000019 00000000  
    |        |     |     |       |--&gt; number of unrecovered RTO timeouts
    |        |     |     |----------&gt; number of jiffies until timer expires
    |        |     |----------------&gt; timer_active (see below)
    |        |----------------------&gt; receive-queue
    |-------------------------------&gt; transmit-queue

1000        0 54165785 4 cd1e6040 25 4 27 3 -1
|          |    |     |    |     |  | |  | |--&gt; slow start size threshold, 
|          |    |     |    |     |  | |  |      or -1 if the threshold
|          |    |     |    |     |  | |  |      is &gt;= 0xFFFF
|          |    |     |    |     |  | |  |----&gt; sending congestion window
|          |    |     |    |     |  | |-------&gt; (ack.quick&lt;&lt;1)|ack.pingpong
|          |    |     |    |     |  |---------&gt; Predicted tick of soft clock
|          |    |     |    |     |              (delayed ACK control data)
|          |    |     |    |     |------------&gt; retransmit timeout
|          |    |     |    |------------------&gt; location of socket in memory
|          |    |     |-----------------------&gt; socket reference count
|          |    |-----------------------------&gt; inode
|          |----------------------------------&gt; unanswered 0-window probes
|---------------------------------------------&gt; uid
</code></pre>
<p>timer_active:
0  no timer is pending
1  retransmit-timer is pending
2  another timer (e.g. delayed ack or keepalive) is pending
3  this is a socket in TIME_WAIT state. Not all fields will contain 
data (or even exist)
4  zero window probe timer is pending</p>
<p>这里介绍一下/proc/[pid]/status文件中关于内存信息的部分。此文件整合了 /proc/[pid]/stat 和 /proc/[pid]/statm 两个文件的内容，以更高的可读性显示信息。</p>
<ul>
<li>内容</li>
<li>原文</li>
</ul>
<h1 id="内容"><a class="header" href="#内容">内容</a></h1>
<p>这里以bash进程对应的status文件为例，介绍一下和内存相关的属性。</p>
<p>下面是实际的一段输出显示</p>
<pre><code class="language-shell">$ cat /proc/27535/status
Name:	bash
Umask:	0022
State:	S (sleeping)
// ...(省略)
VmPeak:	   24944 kB
VmSize:	   24944 kB
VmLck:	       0 kB
VmPin:	       0 kB
VmHWM:	    5640 kB
VmRSS:	    5640 kB
RssAnon:	    1908 kB
RssFile:	    3732 kB
RssShmem:	       0 kB
VmData:	    1880 kB
VmStk:	     132 kB
VmExe:	    1040 kB
VmLib:	    2492 kB
VmPTE:	      88 kB
VmSwap:	       0 kB
// ...(省略)
</code></pre>
<p>各字段的含义如下:</p>
<ul>
<li>Name: 这个进程运行的命令。</li>
<li>State: 进程当前的状态。取值为&quot;R (running)&quot;, &quot;S (sleeping)&quot;, &quot;D (disk sleep)&quot;, &quot;T (stopped)&quot;, &quot;T (tracing stop)&quot;, &quot;Z (zombie)&quot; &quot;X (dead)&quot;中的一个.</li>
<li>VmPeak: 进程占用的虚拟内存大小的峰值</li>
<li>VmSize：进程占用的虚拟内存大小</li>
<li>VmLck: 进程锁定的内存大小（参考mlock(3)）</li>
<li>VmPin: (Linux 3.2添加)。需要直接访问的，固定的不能被移出去的内存大小，</li>
<li>VmHWM: 峰值常驻内存大小。（内存大小的高水印）</li>
<li>VMRSS: 常驻内存大小。这个值是 RssAnon, RssFile, RssShmem三个值的和。</li>
<li>RssAnon: 常驻匿名内存大小</li>
<li>RssFile: 常驻文件映射大小</li>
<li>RssShmem: 常驻共享内存大小</li>
<li>VmData: 数据段内存大小</li>
<li>VmStk: 栈内存大小</li>
<li>VmExe: 代码段大小</li>
<li>VmLib: 动态库内存大小</li>
<li>VmPTE: 页表内存大小</li>
<li>VmSwap: 交换出去的私有匿名内存大小，不包括共享内存交换出去的。</li>
</ul>
<h1 id="原文"><a class="header" href="#原文">原文</a></h1>
<p>可以执行 <code>man 5 proc</code> 查看原文。</p>
<pre><code class="language-text">/proc/[pid]/status
    Provides much of the information in /proc/[pid]/stat and /proc/[pid]/statm in a format that's easier for humans to parse.  Here's an example:

        $ cat /proc/$$/status
        Name:   bash
        Umask:  0022
        State:  S (sleeping)
        Tgid:   17248
        Ngid:   0
        Pid:    17248
        PPid:   17200
        TracerPid:      0
        Uid:    1000    1000    1000    1000
        Gid:    100     100     100     100
        FDSize: 256
        Groups: 16 33 100
        NStgid: 17248
        NSpid:  17248
        NSpgid: 17248
        NSsid:  17200
        VmPeak:     131168 kB
        VmSize:     131168 kB
        VmLck:           0 kB
        VmPin:           0 kB
        VmHWM:       13484 kB
        VmRSS:       13484 kB
        RssAnon:     10264 kB
        RssFile:      3220 kB
        RssShmem:        0 kB
        VmData:      10332 kB
        VmStk:         136 kB
        VmExe:         992 kB
        VmLib:        2104 kB
        VmPTE:          76 kB
        VmPMD:          12 kB
        VmSwap:          0 kB
        HugetlbPages:          0 kB        # 4.4
        Threads:        1
        SigQ:   0/3067
        SigPnd: 0000000000000000
        ShdPnd: 0000000000000000
        SigBlk: 0000000000010000
        SigIgn: 0000000000384004
        SigCgt: 000000004b813efb
        CapInh: 0000000000000000
        CapPrm: 0000000000000000
        CapEff: 0000000000000000
        CapBnd: ffffffffffffffff
        CapAmb:   0000000000000000
        NoNewPrivs:     0
        Seccomp:        0
        Cpus_allowed:   00000001
        Cpus_allowed_list:      0
        Mems_allowed:   1
        Mems_allowed_list:      0
        voluntary_ctxt_switches:        150
        nonvoluntary_ctxt_switches:     545
</code></pre>
<ul>
<li>/proc/net/tcp</li>
</ul>
<h1 id="procnettcp"><a class="header" href="#procnettcp">/proc/net/tcp</a></h1>
<pre><code># cat tcp
sl  local_address rem_address   st tx_queue rx_queue tr tm-&gt;when retrnsmt   uid  timeout inode                                                     
0: 00000000:1A0A 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 322280 1 ffff9d0e587dabc0 100 0 0 10 0                    
1: 3500007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000   101        0 20428 1 ffff9d0e7c3a2bc0 100 0 0 10 0                     
2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 25992 1 ffff9d0e7c3a4ec0 100 0 0 10 0                     
3: 0100007F:1A0A 0100007F:DF1A 08 00000000:00000000 00:00000000 00000000     0        0 322281 1 ffff9d0e7c3a2300 20 4 28 10 -1                   
4: 0100007F:1A0A 0100007F:DF1E 08 00000000:00000000 00:00000000 00000000     0        0 322446 1 ffff9d0e7c3a1180 20 4 28 10 -1                   
5: 0100007F:1A0A 0100007F:DF1C 08 00000000:00000000 00:00000000 00000000     0        0 322313 1 ffff9d0e7c3a4600 20 4 0 10 -1                    
6: 0100007F:1A0A 0100007F:DF32 08 00000000:00000000 00:00000000 00000000     0        0 322559 1 ffff9d0e7c3a3480 20 4 28 10 -1                   
7: 02FEA8C0:0016 01FEA8C0:C151 01 00000000:00000000 02:00000391 00000000     0        0 319989 3 ffff9d0e7c3a6040 24 4 33 10 -1
</code></pre>
<ul>
<li>$2_1: 本地ip,网络字节序</li>
<li>$2_2: 本地port,主机字节序</li>
<li>$3_1: 远端ip,网络字节序</li>
<li>$3_2: 远端port,主机字节序</li>
<li>$4: 套接字状态，状态表参照下面</li>
<li>$5_1: 发送队列中数据长度</li>
<li>$5_2: 接收队列中数据长度</li>
<li>$6_1: 定时器类型，如果为0，表示没有启动定时器。1重传定时器 4表示持续定时器，2表示来连接定时器、FIN_WAIT_2定时器或TCP保活定时器，具体是哪种定时器依赖当前状态，3 表示TIME_WAIT定时器</li>
<li>$6_2: 超时时间单位是jiffies</li>
<li>$7: 超时重传次数</li>
<li>$8: 用户id</li>
<li>$9: 持续定时器或保活定时器周期性发送出去但未被确认的tcp段数据，在收到ACK之后清零</li>
<li>$10:套接字对应的inode</li>
<li>$11: sock结构的引用数</li>
<li>$12: sock结构实例的地址</li>
<li>$13: RTO,单位是clock_t</li>
<li>$14: 用来计算延时确认的估值</li>
<li>$15: 快速确认数和是否启用的标志位的或运算结果</li>
<li>$16: 当前的拥塞窗口大小</li>
</ul>
<table><thead><tr><th>状态值</th><th>状态</th></tr></thead><tbody>
<tr><td>1</td><td>TCP_ESTABLISHED</td></tr>
<tr><td>2</td><td>TCP_SYN_SENT</td></tr>
<tr><td>3</td><td>TCP_SYN_RECV</td></tr>
<tr><td>4</td><td>TCP_FIN_WAIT1</td></tr>
<tr><td>5</td><td>TCP_FIN_WAIT2</td></tr>
<tr><td>6</td><td>TCP_TIME_WAIT</td></tr>
<tr><td>7</td><td>TCP_CLOSE</td></tr>
<tr><td>8</td><td>TCP_CLOSE_WAIT</td></tr>
<tr><td>9</td><td>TCP_LAST_ACK</td></tr>
<tr><td>10</td><td>TCP_LISTEN</td></tr>
<tr><td>11</td><td>TCP_CLOSING</td></tr>
</tbody></table>
<h1 id="linux-proc浅谈"><a class="header" href="#linux-proc浅谈">Linux /proc浅谈</a></h1>
<p>/proc挂在的是一种虚拟的文件系统，和其他的如ext2等其他磁盘文件系统不同的是，/proc挂在的文件系统是面向内存的，存储的是当前内核运行状态的一系列特殊文件，用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息，甚至可以通过更改其中某些文件来改变内核的运行状态。基于/proc文件系统如上所述的特殊性，其内的文件也常被称作虚拟文件，并具有一些独特的特点。例如，其中有些文件虽然使用查看命令查看时会返回大量信息，但文件本身的大小却会显示为0字节。此外，这些特殊文件中大多数文件的时间及日期属性通常为当前系统时间和日期，这跟它们随时会被刷新（存储于RAM中）有关。</p>
<p>/proc目录下，有很多的文件和子目录，下面简要介绍一下这些目录和文件。</p>
<h2 id="进程子目录"><a class="header" href="#进程子目录">进程子目录</a></h2>
<p>/proc/${number}这样的目录表示进程信息的目录，${number}是进程号，这样的目录下包含的都是和当前进程运行状态相关的目录。/proc/${number}的目录结构和/proc差不多，不过/proc下面的文件表示的都是kernel运行相关的状态的，/proc/${number}表示的是进程运行相关状态的。下面介绍几个可以用来分析程序性能的文件。</p>
<pre><code>jackson@jackson-desktop:/proc/3557/net$ ls
anycast6   dev_mcast     icmp      igmp6          ip_mr_cache        ip_tables_targets  netfilter  protocols  raw6       rt_cache  sockstat6     tcp6     udplite6
arp        dev_snmp6     icmp6     ip6_flowlabel  ip_mr_vif          ipv6_route         netlink    psched     route      snmp      softnet_stat  udp      unix
connector  fib_trie      if_inet6  ip6_mr_cache   ip_tables_matches  mcfilter           netstat    ptype      rt6_stats  snmp6     stat          udp6     wireless
dev        fib_triestat  igmp      ip6_mr_vif     ip_tables_names    mcfilter6          packet     raw        rt_acct    sockstat  tcp           udplite  xfrm_stat
</code></pre>
<ol>
<li>statm — 当前进程占用内存的状态信息，通常以&quot;页面&quot;（page）表示, man proc(5)描述如下：</li>
</ol>
<pre><code>Provides information about memory usage, measured in pages.
The columns are:

    size       (1) total program size
                (same as VmSize in /proc/[pid]/status)
    resident   (2) resident set size  // 当前进程实际使用的物理内存大小
                (same as VmRSS in /proc/[pid]/status)
    shared     (3) number of resident shared pages (i.e., backed by a file)
                (same as RssFile+RssShmem in /proc/[pid]/status)
    text       (4) text (code) // 代码段占用的大小
    lib        (5) library (unused since Linux 2.6; always 0)
    data       (6) data + stack // 数据段和栈段数据大小
    dt         (7) dirty pages (unused since Linux 2.6; always 0)
</code></pre>
<ol start="2">
<li>io - 当前进程的磁盘IO情况。man proc(5)描述如下:</li>
</ol>
<pre><code>/proc/[pid]/io (since kernel 2.6.20)
This file contains I/O statistics for the process, for example:

    # cat /proc/3828/io
    rchar: 323934931
    wchar: 323929600
    syscr: 632687
    syscw: 632675
    read_bytes: 0
    write_bytes: 323932160
    cancelled_write_bytes: 0

The fields are as follows:

rchar: characters read
        The number of bytes which this task has caused to be
        read from storage.  This is simply the sum of bytes
        which this process passed to read(2) and similar system
        calls.  It includes things such as terminal I/O and is
        unaffected by whether or not actual physical disk I/O
        was required (the read might have been satisfied from
        pagecache).
        和read_bytes相比，这里也统计了从pagecache中读取的字节数，所以如果只想统计磁盘的IO情况，应该使用read_bytes

wchar: characters written
        The number of bytes which this task has caused, or
        shall cause to be written to disk.  Similar caveats
        apply here as with rchar.

syscr: read syscalls
        Attempt to count the number of read I/O operations—that
        is, system calls such as read(2) and pread(2).

syscw: write syscalls
        Attempt to count the number of write I/O operations—
        that is, system calls such as write(2) and pwrite(2).

read_bytes: bytes read
        Attempt to count the number of bytes which this process
        really did cause to be fetched from the storage layer.
        This is accurate for block-backed filesystems.

write_bytes: bytes written
        Attempt to count the number of bytes which this process
        caused to be sent to the storage layer.

cancelled_write_bytes:
        The big inaccuracy here is truncate.  If a process
        writes 1 MB to a file and then deletes the file, it
        will in fact perform no writeout.  But it will have
        been accounted as having caused 1 MB of write.  In
        other words: this field represents the number of bytes
        which this process caused to not happen, by truncating
        pagecache.  A task can cause &quot;negative&quot; I/O too.  If
        this task truncates some dirty pagecache, some I/O
        which another task has been accounted for (in its
        write_bytes) will not be happening.

Note: In the current implementation, things are a bit racy on
32-bit systems: if process A reads process B's /proc/[pid]/io
while process B is updating one of these 64-bit counters,
process A could see an intermediate result.

Permission to access this file is governed by a ptrace access
mode PTRACE_MODE_READ_FSCREDS check; see ptrace(2).
</code></pre>
<ol start="3">
<li>status — 与stat所提供信息类似，但可读性较好，如下所示，每行表示一个属性信息；其详细介绍请参见 proc的man手册页</li>
<li>limits — 当前进程所使用的每一个受限资源的软限制、硬限制和管理单元；此文件仅可由实际启动当前进程的UID用户读取；（2.6.24以后的内核版本支持此功能）,配合上面的数据，可以做程序的预警功能。</li>
<li>net/dev - 网卡上的接受和发送的数据包情况,和ifconfig统计的数据一样。</li>
</ol>
<pre><code>jackson@jackson-desktop:/proc/3557/net$  cat dev
Inter-|   Receive                                                |  Transmit
 face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
enp0s3: 1028491    1491    0    0    0     0          0         0   148458    1265    0    0    0     0       0          0
    lo:   19092     213    0    0    0     0          0         0    19092     213    0    0    0     0       0          0
enp0s8: 25922534   42792    0    0    0     0          0       212 63034567   46227    0    0    0     0       0          0
</code></pre>
<p>其他的一些文件简介：</p>
<ul>
<li>cmdline — 启动当前进程的完整命令，但僵尸进程目录中的此文件不包含任何信息</li>
</ul>
<pre><code>jackson@jackson-desktop:/proc/3557$ cat cmdline
nc-l6666
</code></pre>
<p>nc -l6666就是我执行的命令。</p>
<ul>
<li>cwd — 指向当前进程运行目录的一个符号链接</li>
</ul>
<pre><code>jackson@jackson-desktop:/proc/3557$ ll | grep cwd
lrwxrwxrwx   1 jackson jackson 0 9月   6 11:24 cwd -&gt; /home/jackson/
</code></pre>
<ul>
<li>exe — 指向启动当前进程的可执行文件（完整路径）的符号链接，通过/proc/N/exe可以启动当前进程的一个拷贝</li>
<li>fd — 这是个目录，包含当前进程打开的每一个文件的文件描述符（file descriptor），这些文件描述符是指向实际文件的一个符号链接, 创建的socket文件描述符也在这里。</li>
<li>task — 目录文件，包含由当前进程所运行的每一个线程的相关信息，每个线程的相关信息文件均保存在一个由线程号（tid）命名的目录中，这类似于其内容类似于每个进程目录中的内容；（内核2.6版本以后支持此功能）</li>
</ul>
<h2 id="其他较常用的文件"><a class="header" href="#其他较常用的文件">其他较常用的文件</a></h2>
<ul>
<li>/proc/cmdline 这个文件给出了内核启动的命令行。它和用于进程的cmdline项非常相似。</li>
<li>/proc/cpuinfo 这个文件提供了有关系统CPU的多种信息。</li>
<li>/proc/meminfo 这个文件给出了内存状态的信息。它显示出系统中空闲内存，已用物理内存和交换内存的总量。它还显示出内核使用的共享内存和缓冲区总量。这些信息的格式和free命令显示的结果类似。</li>
<li>/proc/stat 这个文件包含的信息有CPU利用率，磁盘，内存页，内存对换，全部中断。</li>
<li>/proc/version 这个文件只有一行内容，说明正在运行的内核版本。可以用标准的编程方法进行分析获得所需的系统信息</li>
<li>/proc/filesystems 这个文件列出可供使用的文件系统类型，一种类型一行。虽然它们通常是编入内核的文件系统类型，但该文件还可以包含可加载的内核模块加入的其它文件系统类型。</li>
</ul>
<h2 id="其他较常用的目录"><a class="header" href="#其他较常用的目录">其他较常用的目录</a></h2>
<ul>
<li>/proc/sys子目录 在此目录下有许多子目录。此目录中的许多项都可以用来调整系统的性能。</li>
<li>/proc/net子目录 此目录下的文件描述或修改了联网代码的行为。可以通过使用arp,netstat,route和ipfwadm命令设置或查询这些特殊文件中的许多文件。</li>
</ul>
<p>gcc_gdb</p>
<h1 id="gdb的基本使用"><a class="header" href="#gdb的基本使用">gdb的基本使用</a></h1>
<ul>
<li><a href="https://www.cnblogs.com/arnoldlu/p/9633254.html">调试器GDB的基本使用方法</a></li>
<li><a href="http://www.brendangregg.com/blog/2016-08-09/gdb-example-ncurses.html">gdb Debugging Full Example (Tutorial): ncurses</a></li>
</ul>
<h2 id="基本使用"><a class="header" href="#基本使用">基本使用</a></h2>
<ul>
<li>gdb bin     调试bin</li>
<li>gdb bin core 调试core文件</li>
<li>gdb -p PID 调试进程，需要root权限</li>
<li>b 设置断点</li>
<li>n 下一个流程</li>
<li>s 下一步</li>
<li>p 打印数据，也可以用来执行表达式</li>
<li>c 继续执行</li>
<li>u 继续执行，知道循环结束或者流程结束</li>
<li>i b 显示断点信息</li>
<li>l 显示源代码信息，后边的参数和b一样</li>
</ul>
<h2 id="远程调试"><a class="header" href="#远程调试">远程调试</a></h2>
<ul>
<li>remote host上运行 gdbserver bin  [ip:]port</li>
<li>local host上像调试本地文件一样执行: gdb bin</li>
<li>进入gdb shell之后运行<code>tar remote remote_host:port</code></li>
<li>gdbserver shell会输出建立连接的信息，现在就可以调试远程文件了。</li>
</ul>
<h2 id="汇编调试"><a class="header" href="#汇编调试">汇编调试</a></h2>
<ul>
<li>disas [/m] 显示汇编代码，/m会显示源代码</li>
<li>ni 指令级的单流程执行</li>
<li>si 指令级的单步执行</li>
</ul>
<h2 id="tui"><a class="header" href="#tui">tui</a></h2>
<ul>
<li>可以启动tui来调试代码，或者执行汇编代码</li>
<li>tui regs 可以打开寄存器窗口</li>
</ul>
<p>git check sheet.</p>
<h1 id="git基本要点"><a class="header" href="#git基本要点">git基本要点</a></h1>
<ul>
<li>直接快照，而非比较差异</li>
<li>近乎所有操作都可本地执行</li>
<li>时刻保持数据完整性</li>
<li>多数操作仅添加数据</li>
<li>三种状态  已提交(committed) 已修改(modified) 已暂存(staged)</li>
</ul>
<h1 id="状态流转"><a class="header" href="#状态流转">状态流转</a></h1>
<p>文件在三个工作区域的状态流转和对应的操作</p>
<img src="git/./img/tu1.png" /><h1 id="git配置"><a class="header" href="#git配置">git配置</a></h1>
<ul>
<li>配置命令和配置文件</li>
<li>配置选项</li>
</ul>
<h1 id="配置命令和配置文件"><a class="header" href="#配置命令和配置文件">配置命令和配置文件</a></h1>
<p>git 使用 <code>git config</code>命令来配置配置选项，配置的变量存在于三个位置的文件中。</p>
<ul>
<li>/etc/gitconfig 作用于所有用户。使用 <code>git config --system</code>配置。</li>
<li>~/.gitconfig  作用于当前用户下的所有仓库。使用<code>git config --global</code>配置。</li>
<li>.git/config 作用于当前仓库。使用<code>git config</code>配置。</li>
</ul>
<h1 id="配置选项"><a class="header" href="#配置选项">配置选项</a></h1>
<ul>
<li>用户信息  包括用户名和email,非常重要，每次提交时都会引用这两条信息，表明是谁提交的。</li>
</ul>
<pre><code>git config --global user.name &quot;Jackson Wang&quot;
git config --global user.email &quot;wangjunxiao0207@163.com&quot;
</code></pre>
<ul>
<li>文本编辑器    git需要编辑信息时调用的文本编辑器。</li>
</ul>
<pre><code>git config --global core.editor vim
</code></pre>
<ul>
<li>差异分析工具  在解决合并冲突时才用哪种差异分析工具</li>
</ul>
<p><code>git config --global merge.tool vimdiff</code></p>
<ul>
<li>输出着色      git能够为输出到终端的内容着色。</li>
</ul>
<p><code>git config --global color.ui true</code></p>
<ul>
<li>格式化与空白
<ul>
<li>trailing-space: 默认开启 查找每行结尾的空格</li>
<li>space-before-tab: 默认开启 查找每行开头的制表符前的空格</li>
<li>indent-with-non-tab: 默认关闭 查找8个以上空格开头的行</li>
<li>cr-at-eol: 默认关闭 cr-at-eol表示行尾回车符是合法的(\r)</li>
</ul>
</li>
<li>账号密码管理</li>
</ul>
<p><code>git config --global credential.helper store</code></p>
<p>重置密码管理器: <code>git config --system --unset credential.helper</code></p>
<pre><code># windows
git config --global core.autocrlf true
# Linux  会修正仓库和检出的文件
git config --global core.autocrlf input
# 使用 - 关闭选项
git config --global core.whitespace trailing-space,space-before-tab,-indent-with-non-tab,-cr-at-eol
</code></pre>
<ul>
<li>中文路径      git默认输出的中文会展示成unicode码，导致看不出来原来的文件名，可以使用如下配置修复</li>
</ul>
<p><code>git config --global core.quotepath false</code></p>
<h1 id="总结-1"><a class="header" href="#总结-1">总结</a></h1>
<p>常用配置命令方式:</p>
<pre><code class="language-shell">git config --global user.name &quot;Jackson Wang&quot;
git config --global user.email &quot;wangjunxiao0207@163.com&quot;
git config --global core.editor vim
git config --global merge.tool vimdiff
git config --global color.ui true
# windows
# git config --global core.autocrlf true
# Linux  会修正仓库和检出的文件
git config --global core.autocrlf input
git config --global core.quotepath false
git config --global credential.helper store
</code></pre>
<p>示例配置文件 linux/tool/git/.gitconfig</p>
<p>内容如下:</p>
<pre><code>[user]
	name = Jackson Wang
	email = wangjunxiao0207@163.com
[core]
	editor = vim
	autocrlf = input
	quotepath = false
[merge]
	tool = vimdiff
[color]
	ui = true
</code></pre>
<ul>
<li>git init</li>
<li>git clone remote_url [local name]</li>
<li>git clone --bare remote_url my_project.git  把一个仓库克隆为纯仓库，纯仓库目录名为.git结尾。git服务端常用</li>
<li>git clone --depth=1 remote_url clone最新的一个版本</li>
<li>git status </li>
<li>git add &lt;filename | dir&gt;   如果是dir,会递归跟踪所有该目录下的文件</li>
<li>git add -i  交互模式操作暂存区</li>
<li>git diff  比较的是工作目录和暂存区快照的差异 ，不是和最新提交快照的比较</li>
<li>git diff --staged  比较的是暂存区快照和最新提交快照的差异</li>
<li>git diff --cached  同--staged, --staged 1.6.1之后可以使用</li>
<li>git diff &lt;HEAD~1|HEAD^&gt; HEAD 比较最新提交和上一版本的差别</li>
<li>git commit [-m]</li>
<li>git commit -a      直接把工作区以追踪的文件提交</li>
<li>git rm filename          从暂存区移除，并删除工作目录中的文件</li>
<li>git rm -f filename       如果删除之前修改过并且已经放到暂存区域，则必须要用强制删除选项-f</li>
<li>git rm --cached filename 只存暂存区域删除，不删除工作目录中的文件</li>
<li>git rm log/*.log  可以使用glob模式</li>
<li>git clean -f filename 删除工作区内未跟踪的文件和目录</li>
<li>git mv file_from file_to 可以对文件进行改名或者移动</li>
<li>git log    查看提交历史</li>
<li>git log -p -2  -p显示每次提交的内容差异，用-2则仅显示最近的两次更新</li>
<li>git log --stat  简要显示增改的行数统计</li>
<li>git log --pretty=&lt;oneline|short|full|fuller&gt; 格式化显示历史日志</li>
<li>git commit --amend 使用当前的暂存区快照进行重新提交，相当于撤销最后一次提交操作</li>
<li>git reset HEAD <file>... 取消暂存区的快照</li>
<li>git checkout -- <file>... 取消工作区的修改</li>
<li>git tag   列出现有标签</li>
<li>git tag -l 'v1.4.2.*' 使用特定的搜索模式列出符合条件的标签</li>
<li>git tag -a v1.4 -m 'my version 1.4' 创建一个含附注类型的标签 -a (annotated) 指定标签名字
<ul>
<li>-m 指定对应的标签说明,git会将此说明一同保存在标签对象中</li>
</ul>
</li>
<li>git show v1.4 查看对应标签的版本信息，并连同显示打标签时的提交对象</li>
<li>git tag -s v1.5 -m 'my signed 1.5 tag'  使用私钥来签署标签 -s (signed)</li>
<li>git tag v1.4  创建轻量级标签</li>
<li>git tag -v tag-name 验证已经签署的标签,本地keyring需要有签署者的公钥</li>
<li>git tag -a v1.2 SHA-1-value 为某次提交打标签</li>
<li>git tag -d tag-name 删除tag</li>
<li>git push origin tagname 默认git push 不会把标签推送到远端,需要使用显示推送</li>
<li>git push origin --tags 一次性推送所有本地新增的标签上去</li>
<li>git diff --check  检查暂存区内文件中可能多余的空白字符</li>
<li>git archive master --prefix='project/' | gzip &gt; <code>git describe master</code>.tar.gz 生成压缩包用来发布</li>
<li>git archive master --prefix='project/' --format=zip &gt; <code>git describe master</code>.zip  生成zip格式的压缩包</li>
<li>git log --abbrev-commit --pretty=oneline --abbrev-commit选项会让日志输出中的commit SHA-1值才用简单输出方式</li>
<li>git stash 把工作区已追踪文件和暂存区的索引信息存储起来,并把工作区的内容还原到上一次提交状态</li>
<li>git stash list 查看存储列表</li>
<li>git stash apply [stash@{0}] 把存储内容还原到当前工作区,不会还原暂存区内容。默认使用最后一次存储内容</li>
<li>git stash apply --index  --index选项会把暂存区还原，这样就和存储时的状态一样了</li>
<li>git stash drop stash@{0} 移除存储</li>
<li>git stash pop   移除存储栈顶，并应用其内容,包括暂存区</li>
<li>git stash branch new-branch 使用储藏创建新分支</li>
<li>git blame -L 12,22 simplegit.rb 标注simplegit.rb文件的12到22行之间的内容。SHA-1值前带^符号的表示从未修改过</li>
<li>git blame -C 141,153 GITPackUpload.m  -C参数可以标注出这块代码的原始出处。比如项目内的粘贴复制</li>
<li>bit bisect start  开始二分查找提交历史中最先引入问题的提交，中间使用good和bad告诉git查询结果，用reset结束这次二分查找</li>
<li>git bisect bad    告诉git这次提交是错误的，确定二分查找的下限</li>
<li>git bisect good [v1.0] 告诉git这次提交是正确的，确定二分查找的上限。第一次运行时需要指定本次二分查找的上限</li>
<li>git bisect reset  结束本次二分查找,把HEAD指向原来的位置</li>
<li>git bisect start HEAD v1.0  在HEAD和v1.0提交之间查找</li>
<li>git bisect run test-error.sh 脚本返回0表示工程正常，返回非0表示工程异常，git会自动迭代运行此脚本，直到找到首次问题分支</li>
</ul>
<h1 id="git忽略某些文件"><a class="header" href="#git忽略某些文件">git忽略某些文件</a></h1>
<p>我们可以创建一个名为.gitignore额文件，列出要忽略的文件模式。</p>
<p>.gitignore的格式规范如下:</p>
<ul>
<li>
<p>注释符号是#</p>
</li>
<li>
<p>使用标准的glob模式匹配</p>
<ul>
<li>*匹配零个或多个任意字符</li>
<li>[abc]匹配中括号中的任意一个字符</li>
<li>[0-9a-zA-Z]匹配任意数字或者字母</li>
<li>? 只匹配一个任意字符</li>
</ul>
</li>
<li>
<p>匹配模式最后跟/说明忽略的是目录</p>
</li>
<li>
<p>要忽略指定模式之外的文件模式，可以在模式前加!取反</p>
</li>
</ul>
<pre><code>*.[oa]  # 忽略以.o或者.a结尾的文件
!lib.a  # 不忽略lib.a
/TODO   # 仅仅忽略项目根目录下的TODO文件,不包括subdir/TODO
build   # 忽略 build/ 目录下的所有文件
doc/*.txt # 忽略doc/notes.txt但不包括doc/server/arch.txt
*~      # 忽略所有以~结尾的文件，许多文本编辑器会用这样的文件名保存副本
</code></pre>
<ul>
<li>git branch new-branch-name 创建一个新的分支指针，但是HEAD不会指向新分支</li>
<li>git checkout branch-name 切换分支，使HEAD指向新分支</li>
<li>git checkout -b new-branch-name 创建一个新的分支，并切换分支</li>
<li>git checkout -b new-branch-name origin/branch-name 根据orgin branch-name分支创建新分支</li>
<li>git merge [origin/]target-branch-name 把target-branch-name合并到当前工作分支上</li>
<li>git branch -d branch-name 删除branch-name分支</li>
<li>git mergetool  调用一个可视乎的合并工具来解决所有冲突</li>
<li>git branch  列出当前所有分支</li>
<li>git branch -v 可以查看各个分支最后一次commit信息</li>
<li>git branch --merge|--no-merged  查看和当前分支合并过或者没合并过的分支</li>
<li>git branch -D branch-name 强制删除，如果是没有合并过的分支使用-d删除会失败</li>
<li>git checkout -b new-branch-name origin/branch-name 使用远程仓库的分支创建新的本地分支</li>
<li>git checkout --track origin/branch-name 创建新本地分支，本地分支的名字和远程分支一样</li>
<li>git rebase target-branch-name 把当前分支从共同祖先处产生的提交，提取成差异补丁，然后在target-branch-name分支的最新快照基础上重新应用一遍</li>
<li>git merge --no-commit --squash featureB --squash选项把目标分支上的修改应用到当前分支,--no-commit选项告诉git此时不用自动生成合并提交记录</li>
<li>git cherry-pick SHA-1-value  把SHA-1-value的提交内容应用到当前分支.cherry-pick(挑拣)可以把某一个单独的commit内容应用到当前分支，和rebase，merge不同</li>
<li>git rebase -i HEAD~3  修改最近三次的提交</li>
</ul>
<ul>
<li>git remote  列出远程库的简短名字,origin表示克隆的原始仓库</li>
<li>git remote -v 列出对应的远程地址. -v是--verbose的简写</li>
<li>git remote add short-name url  添加一个新的远程仓库</li>
<li>git fetch [remote-name] 拉去远程库的数据到本地</li>
<li>git fetch --unshallow [remote-name] 克隆最近版本后拉取完整版本</li>
<li>git push [remote-name] [local-branch-name:][remote-branch-name]  把local-branch-name分支数据推送到远程仓库的remote-branch-name分支.默认remote-name是origin,local-branch-nanme是当前工作分支, remote-branch-name是master</li>
<li>git remote show remote-name   查看远程仓库信息</li>
<li>git remote rename origin-remote-name new-remote-name 更改远程仓库的名字</li>
<li>git remote rm remote-name 移除远程仓库</li>
<li>git push origin :remote-branch-name  删除远程分支，省略掉本地分支，等于说提取空白然后把它变成远程分支</li>
<li>git push -f local-branch-name remote-branch-name 强制提交，更改远程分支的提交历史</li>
<li>git pull git://github.com/onetimeguy/project.git  临时抓取远程仓库内容合并到本地，不会添加远程仓库</li>
</ul>
<ul>
<li>git支持的协议</li>
<li>不同协议的使用和优缺点</li>
</ul>
<h1 id="git支持的额协议"><a class="header" href="#git支持的额协议">git支持的额协议</a></h1>
<p>git支持4种协议，如下:</p>
<ul>
<li>本地传输</li>
<li>ssh协议</li>
<li>git协议</li>
<li>http(s)协议</li>
</ul>
<h1 id="不同协议的使用和优缺点"><a class="header" href="#不同协议的使用和优缺点">不同协议的使用和优缺点</a></h1>
<h2 id="本地协议"><a class="header" href="#本地协议">本地协议</a></h2>
<p>远程仓库在该协议种就是磁盘上的另一个目录。通常配合NFS使用。</p>
<p>要从这种仓库里克隆可以这是如下命令:</p>
<pre><code>git clone /opt/git/project.git   # 简单的直接复制
git clone file:///opt/git/project.git # 使用网络传输
</code></pre>
<p>要添加一个本地仓库到现有git工程，运行如下命令:</p>
<pre><code>git remote add local_proj /opt/git/project.git
</code></pre>
<h3 id="优点"><a class="header" href="#优点">优点</a></h3>
<p>基于文件仓库的优点在于它简单使用，权限控制可以使用文件系统的权限控制和网络访问控制</p>
<h3 id="缺点"><a class="header" href="#缺点">缺点</a></h3>
<p>这种方法的缺点是，与基本的网络连接访问相比，能从不同的位置访问的共享权限难以架设。如果你想从家
里的笔记本电脑上推送，就要先挂载远程硬盘，这和基于网络连接的访问相比更加困难和缓慢。
另一个很重要的问题是该方法不一定就是最快的，尤其是对于共享挂载的文件系统。本地仓库只有在你对数
据访问速度快的时候才快。在同一个服务器上，如果二者同时允许 Git 访问本地硬盘，通过 NFS 访问仓库通
常会比 SSH 慢。</p>
<h2 id="ssh协议"><a class="header" href="#ssh协议">ssh协议</a></h2>
<p>ssh协议是一个常用的协议，大多数环境都支持通过ssh对服务器的访问。</p>
<p>通过ssh克隆一个git仓库,如下所示</p>
<pre><code>git clone ssh://user@hostname:project.git
git clone user@hostname:project.git
</code></pre>
<p>不指明使用哪个协议时git默认使用ssh.也可以不指明用户，git默认使用当前登录的用户</p>
<h3 id="优点-1"><a class="header" href="#优点-1">优点</a></h3>
<ul>
<li>如果想拥有对网络仓库的写权限，基本上不可能不使用ssh</li>
<li>ssh访问时安全的</li>
<li>ssh协议传输前会尽可能的压缩数据</li>
</ul>
<h3 id="缺点-1"><a class="header" href="#缺点-1">缺点</a></h3>
<p>不能通过它实现仓库的匿名访问，仅读数据也要能通过ssh访问主机的前提下才能访问仓库，不利于开源项目。</p>
<h2 id="git协议"><a class="header" href="#git协议">git协议</a></h2>
<p>git提供的一个特殊守护进程，它会监听特定端口(9418),不需要授权就能访问仓库。用git协议运营仓库，需要创建git-export-daemon-ok文件--它是协议进程提供仓库服务的必要条件。要么所有人都能克隆git仓库，要么谁也不能。这意味着该协议通常不能用来进行推送，否则知道仓库url的人，人人都能推送了。</p>
<h3 id="优点-2"><a class="header" href="#优点-2">优点</a></h3>
<p>git协议是现存最快的传输协议。</p>
<h3 id="缺点-2"><a class="header" href="#缺点-2">缺点</a></h3>
<p>git协议缺少授权机制。通常配合ssh协议使用，ssh协议开通推送权限，其他人通过git://拥有只读权限。</p>
<p>而且git协议可能也是最难架设的协议。</p>
<h2 id="https-协议"><a class="header" href="#https-协议">HTTP/s 协议</a></h2>
<p>HTTP协议架设简便。只需要把git的纯仓库文件放到HTTP的文件根目录下，配置一个特定的post-update hook就好了。</p>
<p>下面的操作可以运行通过HTTP对仓库进行读取:</p>
<pre><code>cd /var/www/htdocs/
git clone --bare /path/to/git_project gitproject.git
cd gitproject.git
mv hooks/post-update.sample  hooks/post-update
chmod a+x hooks/post-update
</code></pre>
<p>其他人可以使用下面的命令克隆仓库</p>
<pre><code>git clone http://example.com/gitproject.git
</code></pre>
<h3 id="优点-3"><a class="header" href="#优点-3">优点</a></h3>
<ol>
<li>易于架设</li>
<li>占用服务器资源少</li>
<li>可以通过https协议，让传输过程加密</li>
</ol>
<h3 id="缺点-3"><a class="header" href="#缺点-3">缺点</a></h3>
<p>客户端克隆或下载仓库内容慢。</p>
<ul>
<li>搭建ssh协议的服务</li>
<li>搭建http(s)协议的服务</li>
</ul>
<h1 id="搭建ssh协议的服务"><a class="header" href="#搭建ssh协议的服务">搭建ssh协议的服务</a></h1>
<p>todo</p>
<h1 id="搭建https协议的服务"><a class="header" href="#搭建https协议的服务">搭建http(s)协议的服务</a></h1>
<p><a href="git/%E6%90%AD%E5%BB%BA%E8%87%AA%E5%B7%B1%E7%9A%84git_http(s)%E7%A7%81%E6%9C%89%E6%9C%8D%E5%8A%A1%E5%99%A8_%E5%94%90%E9%9C%9C%E7%9A%84%E5%8D%9A%E5%AE%A2.pdf">引用</a></p>
<p>注意的是搭建http协议的git服务，每次在服务器上新建的仓库，需要执行一下hooks/post-update脚本，才能正常clone.</p>
<p>以一个实际的例子，解析一下git diff命令的输出内容格式。</p>
<pre><code>$ git diff HEAD^ HEAD
diff --git a/src/rgw/rgw_quota.cc b/src/rgw/rgw_quota.cc
index 370cf3f1a60..79b5ebebaaf 100644
--- a/src/rgw/rgw_quota.cc
+++ b/src/rgw/rgw_quota.cc
@@ -294,7 +303,8 @@ void BucketAsyncRefreshHandler::handle_response(const int r)
{
if (r &lt; 0) {
    ldout(store-&gt;ctx(), 20) &lt;&lt; &quot;AsyncRefreshHandler::handle_response() r=&quot; &lt;&lt; r &lt;&lt; dendl;
-    return; /* nothing to do here */
+    cache-&gt;async_refresh_fail(user, bucket);
+    return;
</code></pre>
<ul>
<li>
<p>The first line</p>
<p>diff --git a/src/rgw/rgw_quota.cc b/src/rgw/rgw_quota.cc</p>
</li>
</ul>
<p>is a &quot;git diff&quot; header in the form diff --git a/file1 b/file2. The a/ and b/ filenames are the same unless rename/copy is involved (it is not in example). The --git is to mean that diff is in the &quot;git&quot; diff format.</p>
<ul>
<li>
<p>The next line in extended diff header, which is</p>
<p>index f3e63d7..e8f44ba 100644</p>
</li>
</ul>
<p>tell us about mode of given file (100644 means that it is ordinary file and not e.g. symlink, and that it doesn't have executable permission bit), and about shortened hash of preimage (the version of file before given change) and postimage (the version of file after change). This line is used by git am --3way to try to do a 3-way merge if patch cannot be applied itself.</p>
<ul>
<li>Next is two-line unified diff header</li>
</ul>
<pre><code>--- a/src/rgw/rgw_quota.cc
+++ b/src/rgw/rgw_quota.cc
</code></pre>
<p>Compared to diff -U result it doesn't have from-file-modification-time nor to-file-modification-time after source (preimage) and destination (postimage) file names. If file was created the source is /dev/null; if file was deleted, the target is /dev/null.</p>
<p>If you set diff.mnemonicPrefix configuration variable to true, in place of a/ and b/ prefixes in this two-line header you can have instead c/, i/, w/ and o/ as prefixes, respectively to what you compare; see git-config(1)</p>
<ul>
<li>
<p>Next come one or more hunks of differences; each hunk shows one area where the files differ. Unified format hunks starts with line like</p>
<p>@@ -1,8 +1,9 @@</p>
</li>
</ul>
<p>or</p>
<pre><code>@@ -18,6 +19,8 @@ int cmd_http_fetch(int argc, const char **argv, ...
</code></pre>
<p>It is in the format @@ from-file-range to-file-range @@ [header]. The from-file-range is in the form -<start line>,<number of lines>, and to-file-range is +<start line>,<number of lines>. Both start-line and number-of-lines refer to position and length of hunk in preimage and postimage, respectively. If number-of-lines not shown it means that it is 0.</p>
<p>The optional header shows the C function where each change occurs, if it is a C file (like -p option in GNU diff), or the equivalent, if any, for other types of files.</p>
<p>Next comes the description of where files differ. The lines common to both files begin with a space character. The lines that actually differ between the two files have one of the following indicator characters in the left print column:</p>
<ul>
<li>'+' -- A line was added here to the first file.</li>
<li>'-' -- A line was removed here from the first file.</li>
</ul>
<p>So, for example, first chunk</p>
<pre><code> {
 if (r &lt; 0) {
     ldout(store-&gt;ctx(), 20) &lt;&lt; &quot;AsyncRefreshHandler::handle_response() r=&quot; &lt;&lt; r &lt;&lt; dendl;
-    return; /* nothing to do here */
+    cache-&gt;async_refresh_fail(user, bucket);
+    return;
</code></pre>
<p>means that <code>cache-&gt;async_refresh_fail(user, bucket);</code> line was added.</p>
<p>In other words, before the change, the appropriate fragment of then 'rgw_quota.cc' file looked like this:</p>
<pre><code>{
    if (r &lt; 0) {
        ldout(store-&gt;ctx(), 20) &lt;&lt; &quot;AsyncRefreshHandler::handle_response() r=&quot; &lt;&lt; r &lt;&lt; dendl;
        return; /* nothing to do here */
</code></pre>
<p>After the change this fragment of now 'rgw_quota.cc' file looks like this instead:</p>
<pre><code>{
    if (r &lt; 0) {
        ldout(store-&gt;ctx(), 20) &lt;&lt; &quot;AsyncRefreshHandler::handle_response() r=&quot; &lt;&lt; r &lt;&lt; dendl;
        cache-&gt;async_refresh_fail(user, bucket);
        return;
</code></pre>
<ul>
<li>
<p>There might be</p>
<p>\ No newline at end of file</p>
</li>
</ul>
<p>line present (it is not in example diff).
As Donal Fellows said it is best to practice reading diffs on real-life examples, where you know what you have changed.</p>
<h1 id="自动完成"><a class="header" href="#自动完成">自动完成</a></h1>
<p>git源代码中 contrib/completion下有一个git-completion.bash, 把文件复制到用户目录下,并改名成.git-completion.bash。并在.bashrc添加如下内容</p>
<pre><code>source ~/.git-completion.bash
</code></pre>
<p>或者复制到/etc/bash_completion.d/目录下所有用户都用。</p>
<h1 id="修订版本revision选择"><a class="header" href="#修订版本revision选择">修订版本(Revision)选择</a></h1>
<h2 id="单个修订版本"><a class="header" href="#单个修订版本">单个修订版本</a></h2>
<p>可以使用SHA-1值来指明一次提交。不过git提供了人性化的操作。可以使用简短的不短于4个字符的唯一的SHA-1头来表示一个版本。</p>
<pre><code>git show 1c002dd4b536e7479fe34593e72e6c6c1819e53b
git show 1c002dd4b536e7479f
git show 1c002d
</code></pre>
<p>上面的效果是一样的。</p>
<h2 id="祖先引用--和-"><a class="header" href="#祖先引用--和-">祖先引用 ^ 和 ~</a></h2>
<p>另一种指明某次提交的常用方法是通过它的祖先。在引用最后加上一个^，git将其理解为此次提交的父提交。</p>
<p>如果想看上一次提交,可以使用HEAD^,意为&quot;HEAD的父提交&quot;:</p>
<pre><code>git show HEAD^
</code></pre>
<p>也可以在^后添加一个数字--如，d921970^2意为&quot;d921970的<strong>第二父提交</strong>&quot;。这种语法只在合并提交时有用，因为合并提交可能有多个父提交。第一父提交是你合并时所在分支，而第二父提交是你所合并的分支。</p>
<pre><code>git show HEAD^2
</code></pre>
<p>另外一个指明祖先提交的方法是~。也指向第一父提交，HEAD~与HEAD^是等价的，但是指定数字时，HEAD~2表示的是&quot;第一父提交的第一父提交&quot;，和HEAD^^效果一样。</p>
<p>~和^也可以配合使用,HEAD~3^2表示HEAD~3提交引用的第二父提交。</p>
<h1 id="git分支操作"><a class="header" href="#git分支操作">git分支操作</a></h1>
<p>分支模型是git核心必杀技。</p>
<ul>
<li>git中的对象类型</li>
<li>仓库历史</li>
<li>分支
<ul>
<li>HEAD</li>
</ul>
</li>
</ul>
<h1 id="git中3种对象类型"><a class="header" href="#git中3种对象类型">git中3种对象类型</a></h1>
<ul>
<li>表示文件快照内容的blob对象类型</li>
<li>表示记录目录结构内容及其中各个文件对应的blob对象索引的tree对象类型</li>
<li>表一个包含指向tree对象的索引和其他提交信息(如提交的注释)元数据的commit对象。</li>
</ul>
<h1 id="仓库历史"><a class="header" href="#仓库历史">仓库历史</a></h1>
<p>第一次提交后，仓库历史如图所示。第一个commit对象没有指向parent的指针。</p>
<img src="git/./img/commit-and-tree.png">
<p>第二次提交后，仓库历史如图所示。除了第一个commit对象都会一个指向parent的指针。
<img src="git/./img/commits-and-parents.png"></p>
<h1 id="分支"><a class="header" href="#分支">分支</a></h1>
<p>分支本质上是一个指向commit对象的可变指针。git使用master作为主分支的默认名字，它在每次提交的时候都会自动向前移动。</p>
<p>当有多个分支时，git是怎么知道你在哪个分支上进行操作的呢?肯定需要一个变量记录一下，这个变量就是HEAD.</p>
<ul>
<li>修订版本(Revision)选择  HEAD ^ ~</li>
<li>交互式暂存   git add -i</li>
<li>储藏(stash)  git stash</li>
<li>重写提交历史 git rebase -i</li>
<li>使用git调试
<ul>
<li>文件标注 git blame</li>
<li>二分查找 git bisect</li>
</ul>
</li>
<li>子模块 git submodule</li>
<li>子树合并   todo</li>
</ul>
<p>把一个分支整合到另一个分支上有两种操作: merge(合并)和rebase(衍合).</p>
<p>merge是一个三方合并的过程，即branch1,branch2与它们的共同祖先合并。</p>
<p>rebase是把当前分支与目标分支共同祖先之后的提交，提取成临时差异文件,然后切换到目标分支，把那些差异文件依次施加到目标上。</p>
<p>如把experiment分支衍合到master分支</p>
<pre><code>git checkout experiment
git rebase master
</code></pre>
<p>就会把experiment整合到master分支上，衍合后的最终结果和master merge experiment一样，只是提交历史是线性的。</p>
<p>注意: 不要衍合那些已经提交到公共仓库的更新。</p>
<p>extend</p>
<p>这里分析一下，基于http协议，git clone, fetch, push过程中请求格式长啥样。</p>
<ul>
<li>搭建环境</li>
<li>clone
<ul>
<li>clone 空仓库</li>
<li>clone 非空仓库</li>
</ul>
</li>
<li>push</li>
<li>fetch</li>
</ul>
<h1 id="搭建环境"><a class="header" href="#搭建环境">搭建环境</a></h1>
<ul>
<li>web服务:nginx, 端口8008</li>
<li>服务端: Linux 192.168.56.109</li>
<li>客户端: Win10 192.68.56.1</li>
<li>git仓库: git_test.git</li>
<li>网络分析工具: wireshark</li>
</ul>
<p>nginx相关配置:</p>
<pre><code>server {
        listen       8008;

        location / {   
                root   /opt/git;
        }
}
</code></pre>
<p>在服务器端创建仓库：</p>
<pre><code>cd /opt/git
mkdir git_test.git
cd git_test.git
git init --bare
</code></pre>
<h1 id="clone请求格式"><a class="header" href="#clone请求格式">clone请求格式</a></h1>
<p>客户端执行<code>git clone http://192.168.56.109:8008/git_test.git</code>命令，http交互内容如下所示:</p>
<pre><code>GET /git_test.git/info/refs?service=git-upload-pack HTTP/1.1
Host: 192.168.56.109:8008
User-Agent: git/2.17.1
Accept: */*
Accept-Encoding: gzip
Accept-Language: C, *;q=0.9
Pragma: no-cache

HTTP/1.1 200 OK
Server: nginx/1.14.0 (Ubuntu)
Date: Sat, 22 May 2021 06:36:04 GMT
Content-Type: application/octet-stream
Content-Length: 0
Last-Modified: Sat, 22 May 2021 06:21:18 GMT
Connection: keep-alive
ETag: &quot;60a8a2de-0&quot;
Accept-Ranges: bytes

GET /git_test.git/HEAD HTTP/1.1
Host: 192.168.56.109:8008
User-Agent: git/2.17.1
Accept: */*
Accept-Encoding: gzip
Accept-Language: C, *;q=0.9
Pragma: no-cache

HTTP/1.1 200 OK
Server: nginx/1.14.0 (Ubuntu)
Date: Sat, 22 May 2021 06:36:04 GMT
Content-Type: application/octet-stream
Content-Length: 23
Last-Modified: Sat, 22 May 2021 06:21:02 GMT
Connection: keep-alive
ETag: &quot;60a8a2ce-17&quot;
Accept-Ranges: bytes

ref: refs/heads/master
</code></pre>
<p>clone操作，会产生两个http请求, 一个是<code>GET /git_test.git/info/refs?service=git-upload-pack</code>获取refs内容，由于是一个空仓库，返回的内容是空。另一个是<code>GET /git_test.git/HEAD</code>,获取当前使用的主分支是谁，默认是master.</p>
<p>我们在服务器端给远程仓库增加提交历史和增加分支。目前远程仓库状态如下:</p>
<pre><code>$ git branch -a
  branch2
* master
  remotes/origin/branch2
  remotes/origin/master

$ git log origin/master --pretty=oneline 
a18cf8399ac101e1046b20fd985b01771b797f98 (HEAD -&gt; master, origin/master) init commit
$ git log origin/branch2 --pretty=oneline 
02bb561861e939d0b67fe1eb309024055a8c909a (origin/branch2, branch2) branch2 first commit
a18cf8399ac101e1046b20fd985b01771b797f98 (HEAD -&gt; master, origin/master) init commit
</code></pre>
<p>此时，我们重新clone一下，看看请求传输过程。</p>
<p>数据太多，这里给个数据文件的链接。</p>
<p><a href="git/extend/./clone_no_empty.txt">clone_no_empty.txt</a></p>
<p>从上面文件的数据显示，git clone 会refs信息，也就是仓库中分支信息，然后获取HEAD指向的分支，默认是HEAD指向master, 然后开始请求对应的文件快照数据。</p>
<h1 id="push操作"><a class="header" href="#push操作">push操作</a></h1>
<p>搭建基于http协议的push操作，需要一些步骤，就不验证了</p>
<h1 id="fetch操作"><a class="header" href="#fetch操作">fetch操作</a></h1>
<p>和clone操作一样, 先获取refs信息,然后获取HEAD信息，然后在获取差异的文件快照数据.</p>
<p>GnuPG软件（简称GPG），它是目前最流行、最好用的加密工具之一。</p>
<p>要了解什么是GPG，就要先了解PGP。</p>
<p>1991年，程序员Phil Zimmermann为了避开政府监视，开发了加密软件PGP。这个软件非常好用，迅速流传开来，成了许多程序员的必备工具。但是，它是商业软件，不能自由使用。所以，自由软件基金会决定，开发一个PGP的替代品，取名为GnuPG。这就是GPG的由来。</p>
<p>http://www.ruanyifeng.com/blog/2013/07/gpg.html</p>
<p>language</p>
<p>language</p>
<h1 id="4个类型转换关键字"><a class="header" href="#4个类型转换关键字">4个类型转换关键字</a></h1>
<ul>
<li>static_cast</li>
<li>dynamic_cast</li>
<li>reinterpret_cast</li>
<li>const_cast</li>
</ul>
<h2 id="static_cast"><a class="header" href="#static_cast">static_cast</a></h2>
<p>static_cast是在编译期做的转换。如果是基本类型直接就是赋值，如果是指针类型的，就是把地址直接赋值给了新值，不做任何判断，和C语言中的强转一样。示例代码:</p>
<pre><code class="language-c++">int i = 10;
short s = static_cast&lt;short&gt;(i);
A* a = new B(10);
B* b = static_cast&lt;B*&gt;(a);
</code></pre>
<p>反汇编代码如下:</p>
<pre><code class="language-asm">    int i = 10;
00491C17 C7 45 EC 0A 00 00 00 mov         dword ptr [i],0Ah  
    short s = static_cast&lt;short&gt;(i);
00491C1E 66 8B 45 EC          mov         ax,word ptr [i]  
00491C22 66 89 45 E0          mov         word ptr [s],ax
// 跳过其他代码
    B* b = static_cast&lt;B*&gt;(a);
00491C81 8B 45 D4             mov         eax,dword ptr [a]  
</code></pre>
<h2 id="dynamic_cast"><a class="header" href="#dynamic_cast">dynamic_cast</a></h2>
<p>dynamic_cast是用来做基类指针和子类指针互相转换的，当子类指针转换成基类指针时同static_cast，直接在编译期完成，当基类指针转换成子类指针时是运行时做转换的，有类型判断，如果转换失败返回NULL。示例代码:</p>
<pre><code class="language-c++">A* a = new B(10);
B* b = dynamic_cast&lt;B*&gt;(a);
A* a2 = dynamic_cast&lt;A*&gt;(b);
</code></pre>
<p>反汇编代码:</p>
<pre><code class="language-asm">    B* b = dynamic_cast&lt;B*&gt;(a);
009C1C72 6A 00                push        0  
009C1C74 68 4C B1 9C 00       push        offset B `RTTI Type Descriptor' (09CB14Ch)  
009C1C79 68 38 B1 9C 00       push        offset A `RTTI Type Descriptor' (09CB138h)  
009C1C7E 6A 00                push        0  
009C1C80 8B 45 EC             mov         eax,dword ptr [a]  
009C1C83 50                   push        eax  
009C1C84 E8 A0 F7 FF FF       call        ___RTDynamicCast (09C1429h)  
009C1C89 83 C4 14             add         esp,14h  
009C1C8C 89 45 E0             mov         dword ptr [b],eax  
    A* a2 = dynamic_cast&lt;A*&gt;(b);
009C1C8F 8B 45 E0             mov         eax,dword ptr [b]  
009C1C92 89 45 D4             mov         dword ptr [a2],eax
</code></pre>
<p>可以看到父类转到子类时，是调用___RTDynamicCast函数来转换的，把结果存储到eax中，然后赋值给b。</p>
<h2 id="reinterpret_cast"><a class="header" href="#reinterpret_cast">reinterpret_cast</a></h2>
<p>reinterpret_cast通常为操作数的位模式提供较低层的重新解释,主要是将数据从一种类型的转换为另一种类型。所谓“通常为操作数的位模式提供较低层的重新解释”也就是说将数据以二进制存在形式的重新解释。</p>
<pre><code class="language-asm">    int i;
    const char* p = &quot;This is an example.&quot;;
002D1988 C7 45 EC 30 8B 2D 00 mov         dword ptr [p],offset string &quot;This is an example.&quot; (02D8B30h)  
    i = reinterpret_cast&lt;int&gt;(p);
002D198F 8B 45 EC             mov         eax,dword ptr [p]  
002D1992 89 45 F8             mov         dword ptr [i],eax  
</code></pre>
<h2 id="const_cast"><a class="header" href="#const_cast">const_cast</a></h2>
<p>cosnt_cast是用来去除const属性的，也是编译期语法。</p>
<pre><code class="language-asm">    int* b = const_cast&lt;int*&gt;(a);
00DF19CE 8B 45 F8             mov         eax,dword ptr [a]  
00DF19D1 89 45 EC             mov         dword ptr [b],eax
</code></pre>
<h1 id="rtti"><a class="header" href="#rtti">RTTI</a></h1>
<p>run-time-type-identify, 运行时类型标识。程序能够使用基类的指针或引用来检查着这些指针或引用所指的对象的实际派生类型。RTTI中两个操作符分别是: typeid和dynamic。</p>
<ul>
<li>RTTI的实现机制</li>
<li>RTTI验证</li>
</ul>
<h2 id="rtti的实现机制"><a class="header" href="#rtti的实现机制">RTTI的实现机制</a></h2>
<p>如何通过一个指针鉴别出这是一个什么类型的对象呢？我们知道对象的内存中只有成员变量，以及有虚函数时，会有一个虚表指针，通过成员变量肯定是不能鉴别出这个父类还是哪个具体的子类，因为通过父类指针只能访问的到父类中有的字段，子类和父类一样的，完全看不出来，那只能靠虚表指针了，毕竟虚表的不同才能表示操作不同，表示不同类型的类。这样说的话，没有虚表指针的类无法进行RTTI判断。事实确实如此:</p>
<pre><code class="language-c++">class Clerk
{
public:
    ~Clerk()
    {
        cout &lt;&lt; &quot;i am clerk&quot; &lt;&lt; endl; 
    }
};

class WangClerk : public Clerk
{
public:
    ~WangClerk()
    {
        cout &lt;&lt; &quot;i am wang&quot; &lt;&lt; endl;
    }
};

int main()
{
    Clerk* p = new WangClerk;
    WangClerk* pw = dynamic_cast&lt;WangClerk*&gt;(p); // 这里会报dynamic_cast必须是多态类型的错误。
}
</code></pre>
<p>上面的小实验验证了运行时类型识别和类的虚表有关，更详细的dynamic_cast如何校验类型的，可以反汇编<code>WangClerk* pw = dynamic_cast&lt;WangClerk*&gt;(p);</code>一探究竟，这里汇编代码有点多，而且汇编的符号展示有问题，就算贴出来汇编代码，可能也难以理解，还是靠自己研究吧，这里直接说结论吧:确实使用了一个RTTI Type Descriptor的数据描述了类型，其地址放在了虚表的前4字节。流程如下:通过类对象的首4字节找到虚表，然后在虚表地址的前4字节找到具体类型的descriptor地址，拿到dscriptor后后边的问题就简单了，具体的descriptor的数据结构没有去研究。</p>
<h2 id="rtti验证"><a class="header" href="#rtti验证">RTTI验证</a></h2>
<p>可以做三个实验，来验证dynamic的实现机制。实验一：用子类的虚表地址赋值给父类对象，查看dynamic是否会把父对象当作子类，答案当然是会。</p>
<pre><code class="language-c++">Parent *p = new Parent;
*(void**)p = *(void**)(new Child);
cout &lt;&lt; dynamic&lt;Child&gt;(p) &lt;&lt; endl;
</code></pre>
<p>这也符合常理，虚表都变成子类的虚表了，它当然就是子类，当然如果你把它转成子类，如果访问了子类特有的属性，那就出问题了，毕竟父类对象创建时可没申请子类特有的字段。</p>
<p>实验二: 我们不修改虚表地址，我们修改虚表地址前4字节，让他变成子类虚表地址的前4字节，结果也是会当成子类，因为虚表是只读的，只能在内存窗口中修改数据这里就不掩饰了。</p>
<h1 id="const"><a class="header" href="#const">const</a></h1>
<ul>
<li>const类型指针变量与const指针变量</li>
<li>const修饰的变量真的无法修改吗？</li>
</ul>
<h2 id="const类型指针变量与const指针变量"><a class="header" href="#const类型指针变量与const指针变量">const类型指针变量与const指针变量</a></h2>
<pre><code class="language-c">int a = 10;
const int * p = &amp;a; // const类型指针
int* const p = &amp;a; // const指针
</code></pre>
<p>当const修改指针的类型时，表示不能通过这个指针修改所指的内存，但是指针变量可以被赋值为新值；当const修饰变量时，表示这个指针变量不能被赋值为新的值，但是可以可以通过这个指针修改所指的内存。</p>
<h2 id="const修饰的变量真的无法修改吗"><a class="header" href="#const修饰的变量真的无法修改吗">const修饰的变量真的无法修改吗？</a></h2>
<p>我们知道const修饰的变量不使用const_cast的情况下是无法更改的，那么有什么操作来修改const变量呢？我们只需要明白一个道理就行了:const是编译期语法，运行时是不会控制到几字节级别的内存读写的。只要我们能通过编译器允许的语法拿到const变量的地址，就可以修改这个地址上的内存数据。示例代码如下:</p>
<pre><code class="language-c">int main()
{
    const int i = 100;
    int* ip = (int*)(long)&amp; i;
    *ip = 200;
    const int* p = &amp;i;
    printf(&quot;%d %d\n&quot;, i, *p);
    return 0;
}
</code></pre>
<p>这里可以猜测一下输出结果是什么，按理来说应该是<code>200 200</code>,但是实际结果确实<code>100 200</code>,感觉好奇怪，<code>i</code>好像没被修改，但是好像又被修改了。解释如下，首先这样写<code>int* ip = &amp;i;</code>是不合法的，但是我们知道在x64环境下，指针大小是8字节，并且指针类型和整形可以互转的语法拿到i的地址，然后这个这个地址上的数据，把它变成了200，然后我们以两种方式打印了i上的值，一种是直接访问的方式，另一种是通过指针间接方式。但是第二种输出方式是符合预期的，第一种却还是100，我的主观猜测是因为i是const类型，所以编译代码时，对于使用i值的地方只用用100(0x64)替换了,因为编译器认为i是const类型，不会被修改，直接把它的值拿过来当作立即数用，比访问内存有效多了。我们通过反汇编验证一下:</p>
<pre><code class="language-asm">    printf(&quot;%d %d\n&quot;, i, *p);
009E188E 8B 45 DC             mov         eax,dword ptr [p]  
009E1891 8B 08                mov         ecx,dword ptr [eax]  
009E1893 51                   push        ecx  
009E1894 6A 64                push        64h  
009E1896 68 CC 7B 9E 00       push        offset string &quot;%d\n&quot; (09E7BCCh)  
009E189B E8 F8 FA FF FF       call        _main (09E1398h)  
009E18A0 83 C4 0C             add         esp,0Ch
</code></pre>
<p>我们看到*p是通过取内存的方式获取的值来压入栈的，i的地方确实是直接压入0x64,证明我们上面的猜想是正确的。</p>
<p>综上，我们是可以修改const变量的，但是我们也注意到了，编译器生成代码时，有时会直接使用const变量的值，而不是从内存中取。</p>
<h1 id="内存对齐"><a class="header" href="#内存对齐">内存对齐</a></h1>
<h2 id="什么是对齐"><a class="header" href="#什么是对齐">什么是对齐</a></h2>
<p>基本数据(如单字节、双字节、四字节整数)存放处的地址必须能被自己数据类型的大小整除。比如，双字节整数存放的地址必须被2整除，四字节整数存放的地址要被4整除。如果不满足要求，不同体系结构的CPU反应不同，有的直接抛出异常，有的可以执行，但是效率变慢如x86.</p>
<h2 id="为什么需要对齐"><a class="header" href="#为什么需要对齐">为什么需要对齐</a></h2>
<p><a href="http://junyelee.blogspot.com/2019/01/memory-alignment-issue.html">参考</a></p>
<h2 id="结构体的对齐规则"><a class="header" href="#结构体的对齐规则">结构体的对齐规则</a></h2>
<ol>
<li>首先选定一个盒子，然后依序将字段往盒子中放，当盒子放不下后，再用另一个盒子存放，直至所有字段都存放完毕。</li>
</ol>
<p>盒子长度=min{max{sizeof(成员变量)}, 对齐长度}</p>
<p>max{sizeof(成员变量)}代表取所有成员变量大小的最大值。</p>
<ol start="2">
<li>成员放入盒子的规则:</li>
</ol>
<p>离盒子头部偏移字节数=n*sizeof(成员变量)，n=0,1,2,...</p>
<h2 id="结构体对齐带来的问题"><a class="header" href="#结构体对齐带来的问题">结构体对齐带来的问题</a></h2>
<p>大部分情况下，我们都感受不到结构体的对齐会有啥影响，填充的那些空白字节，我们不会去操作它，但是在某些场合下，这种填充我们无法忽视。</p>
<p>我们定了一个网络协议，数据包的第一字节是数据类型，第2，3字节表示数据包的长度，我们在实现时往往都是使用一个struct来表示这个协议:</p>
<pre><code>struct Proto {
    char type;
    short len;
}
</code></pre>
<p>如果不设置编译器的生成代码的对齐方式，就会因为对齐而造成无法正确解析数据包内容。定的协议头的大小是3字节，但是我们从网络数据包读取时会读取4字节，type后边补了一字节，关键是len的大小不能真正的表达数据包的大小。因为表示长度的2byte数据，1byte存在了type后边的填充位置上，1byte变成了len字段的底8位，然后把数据区的第一个字节当成了高8位。我们修改一下协议，变成如下:</p>
<pre><code>struct Proto {
    short len;
    char type;
}
</code></pre>
<p>这样协议头的读取是读取争取的了，但是我们夺取数据时大部分都是这样写的<code>char *data = (char*)packet + sizeof(struct Proto);</code>去掉协议头就是数据了，但是因为对齐，struct Proto的大小是4，这样数据就少读了1byte,这就出现问题了。怎样解决呢？</p>
<ul>
<li>方式1：设置编译器的对齐大小是1，这样结构体的大小就是所见即所得了。
<ul>
<li>gcc中代码中使用<code>#pragma pack(1)</code>宏来使下面的代码按照1byte对齐的方式编译</li>
<li>vs中项目属性-&gt;配置属性-&gt;C/C++-&gt;代码生成-&gt;结构体成员对齐-&gt;1字节
这种是需要写代码人的保证的，从工程角度看，需要靠人保证的总会出点问题。另一种方式就是在协议上动动手，不让结构体发生补位。</li>
</ul>
</li>
<li>方式2：修改协议</li>
</ul>
<pre><code>struct Proto {
    char type;
    char reserved[3];
    int len;
};
</code></pre>
<p>主动在协议中定义3字节的保留字段，不让编译器自己填充，这样也是所见即所得，比靠人写代码保证可靠多了。</p>
<p>参考TCP/IP协议族的参考书，其中有很多用图展示的协议:一行4字节，协议中字段排雷非常&quot;整齐&quot;,没有“错位”的感觉，其实那就是无填充对齐的最形象展示。</p>
<h1 id="拷贝赋值"><a class="header" href="#拷贝赋值">拷贝赋值</a></h1>
<p>当我们对类的copy constructor和operator assign方法不进行处理的话，使用一个对象初始化另一个对象，或者一个对象赋值另一个对象时，只是简单的把所有的成员简单的复制一遍。如果成员中有共享的资源时，这种默认方式就会造成问题。可以参考一下<a href="language/c_plus_plus/./%E6%9E%84%E9%80%A0%E6%9E%90%E6%9E%84.html">构造析构</a>的示例代码，一个简单的共享内存资源的问题。</p>
<h2 id="copy-constructor"><a class="header" href="#copy-constructor">copy constructor</a></h2>
<p>拷贝构造函数是在用一个对象初始化另一个对象时调用的。示例代码如下:</p>
<pre><code class="language-c">class Test
{
public:
    int i1;
    int i2;
    Test()
    {
    }
    Test(Test&amp; right)
    {
        i1 = right.i1;
        i2 = right.i2;
        cout &lt;&lt; &quot;Test copy constructor&quot; &lt;&lt; endl;
    }
    Test&amp; operator =(const Test&amp; right)
    {
        cout &lt;&lt; &quot;Test equal method&quot; &lt;&lt; endl;
        if (this == &amp;right)
        {
            return *this;
        }
        i1 = right.i1;
        i2 = right.i2;
        return *this;
    }
};

int main()
{
    Test t1;
    t1.i1 = 1;
    t1.i2 = 2;

    Test t2 = t1;
    
    Test t3;
    t3 = t1;

    return 0;
}
</code></pre>
<p>如果写了copy constructor时，当执行<code>Test t2 = t1;</code>时就会调用copy constructor;参数传递时，也会调用。</p>
<h2 id="operator-assign"><a class="header" href="#operator-assign">operator assign</a></h2>
<p>当执行<code>t3 = t1;</code>时会调用拷贝赋值方法。</p>
<p>copy constructor和operator assign作用是一样的，只是在不同的条件下触发的，我们为了避免写重复的代码，可以在copy constructor中调用operator assign。</p>
<pre><code class="language-c++">Test(Test&amp; right)
{
    // call assign
    *this = right;
    cout &lt;&lt; &quot;Test copy constructor&quot; &lt;&lt; endl;
}
</code></pre>
<p>如果把copy constructor和operator assign定义为private,就可以防止用一个对象初始化另一个变量和对象间的赋值操作了。</p>
<h1 id="c中的指针"><a class="header" href="#c中的指针">C中的指针</a></h1>
<ul>
<li>指针的大小和类型</li>
<li>指针类型强转</li>
<li>void *</li>
<li>函数指针探究</li>
</ul>
<p>以下的示范代码是在vs2019上以x86形式运行的。虽然现在有很多强大的c/c++ IDE(如CLion),但是visual studio的可视化调试还是非常好用啊(我开发的是运行在Linux的程序，不能使用VS的调试，所以还是喜欢CLion)。</p>
<p>我们都知道指针变量中存储的是内存地址，指针变量有很多种类型，不同指针类型的变量不能相互赋值，需要强转。下面我们从底层的角度来解读C语言中是怎样包装指针的。</p>
<h2 id="指针的大小"><a class="header" href="#指针的大小">指针的大小</a></h2>
<p>指针存储的是地址，而且有不同的类型，int*, float* 等，所以最直观的猜测指针类型应该有两个属性:</p>
<pre><code>struct Pointer
{
    int address; // 地址，这里是在x86环境下，32位大小的数字就能表示所有的地址
    int type;    // 指针类型
}
</code></pre>
<p>这样我们就能通过地址和类型知道操作的内存块(字面意义的内存块，不是操作系统中的内存管理中的块)了，比如从int*型的指针指向的内存取值，我们就可以从其指向的地址处连读4字节的数据出来。这样的设想很合理，那我们就来验证以下指针是不是这样实现的。最简单的方式就是打印以下各种指针类型的大小。</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;
void main() {
    printf(&quot;%d %d\n&quot;, sizeof(int*), sizeof(float*));
    getchar();
}
</code></pre>
<p>输出是:<code>4 4</code>。</p>
<p>这结果证实了，不同的指针类型大小确实是一样的，但是奇怪的是指针类型的大小只有4，我们知道光存储地址就要花费4B,它是如何只使用4byte既能维护地址信息，又能维护大小信息的呢？这是什么神奇的魔法或者什么骚操作？我们深入到汇编层看看去。</p>
<pre><code class="language-c">int gi;
int* pi;
void main() {
    pi = &amp;gi;
    *pi = 12;
}
</code></pre>
<p>在vs中调试上边的代码，打个断点，打开反汇编窗口，可以看到编译后的汇编代码,下面是截取的部分汇编片段，关闭了<strong>显示符号名</strong>选项。</p>
<pre><code>    pi = &amp;gi;
00341718 C7 05 E8 A4 34 00 E4 A4 34 00 mov         dword ptr ds:[0034A4E8h],34A4E4h  
    *pi = 12;
00341722 A1 E8 A4 34 00       mov         eax,dword ptr ds:[0034A4E8h]
00341727 C7 00 0C 00 00 00    mov         dword ptr [eax],0Ch  
</code></pre>
<p><code>pi = &amp;gi;</code>这条c语句对应的汇编是下面的一行，分为了3个部分最左边的<code>00341718</code>表示内存地址，<code>C7 05 E8 A4 34 00 E4 A4 34 00</code>表示机器码,<code>mov         dword ptr ds:[0034A4E8h],34A4E4h</code>就是我们的汇编指令了，通过<strong>监视</strong>窗口，打印出&amp;pi,pi,&amp;gi的值，我们知道<code>0034A4E8h</code>是pi变量的地址，<code>34A4E4h</code>是gi变量的地址，这条汇编指令就是把gi变量的值赋值给了pi,而且是<code>dword ptr</code>大小的，就是4byte,pi变量4字节的空间放上了gi的地址，没有留下剩余的空间维护大小这个信息。那它取值时是怎样取4字节大小的，存储时存放4字节大小的？</p>
<p>下面一句就是赋值语句，我们来研究一下。这条赋值语句变成了两条汇编指令，第一条指令把<code>[0034A4E8h]</code>处的数据存到eax中，从上一条汇编指令中可以看到<code>[0034A4E8h]</code>存的就是gi的地址；第二条指令把0Ch存入了[eax]内存中，就是存入了gi中，前面有个修饰符<code>dword ptr</code>,表示这是一个双字节大小的内存块，来存储0x0Ch。这我们就知道了，原来指针变量不维护类型这个信息，只有存取时才使用这个大小，我指针变量只存地址，由编译器生成汇编代码时，根据指针类型来生成对应的存多大，取多大的汇编代码。</p>
<p>既然指针类型只存储一个地址数据，为啥不同类型的指针变量不能直接赋值呢？</p>
<h2 id="指针的强转"><a class="header" href="#指针的强转">指针的强转</a></h2>
<p>如果真的明白了上面的实验，这非常好解释了，一个char*的指针变量赋值给了一个int*的变量，那使用int*操作时，编译器都会生成按照4字节操作的汇编指令，但是你的变量只是一个char,1byte大小，这操作不是内存越界了吗，所以编译器就不让你这样干，编译时给你报错。</p>
<p>但是指针类型支持强制转换，比如把一个int*的变量强制转成一个一个char*的，我只想操作第一个字节，访问数据的汇编指令按照1字节大小给我生成，我只想操作第一个字节的内存，这完全的正确，你很明确这不会发生内存越界。但是char*转成int*,是危险的，如果char*只是指向的一个字节，或者指向的字节数组小于4byte,这就存在内存的越界访问，编译器不好判断char*指向的内存是多大的，所以编译器选择相信写出这样代码的人，也允许编译通过。</p>
<h2 id="void"><a class="header" href="#void">void*</a></h2>
<p>void* 是一种特殊的指针类型，只能用来存储地址，不能操作其指向的那块内存，因为编译器无法根据其类型生成操作多大字节的汇编代码，是byte, word,还是dword?如果要操作其指向的那块内存，必须要把它转成一种特定的指针类型，这样编译器才知道生成什么样的汇编代码。</p>
<p>既然void*只是用来存储一个地址，那我使用int类型也可以存储(x86上)，确实如此，我也经常使用int来存储地址(需要根据地址值做算术运算时，int存储地址比较方便，写底层代码时使用较多)，void*比直接使用int有优势的一个地方是，跨平台性高，在x86机器上void*是4byte大小，在x64机器上void*是8byte大小，int还只是4byte大小，在x64上int是存储不了地址的，需要使用long类型了。</p>
<h2 id="函数指针探究"><a class="header" href="#函数指针探究">函数指针探究</a></h2>
<p>todo</p>
<h1 id="构造析构"><a class="header" href="#构造析构">构造析构</a></h1>
<p>c++的类在创建对象时，会自动调用构造函数，在对象离开作用域时会自动调用析构函数来释放资源。我们可以利用这个特性做一些资源管理的自动化事情，比如在构造函数中打开文件，在析构中关闭文件；或者锁的获取和释放等等。但是使用不好，会造成奇怪的问题。</p>
<p>如果类没有写拷贝构造函数或者= operator,当发生变量赋值或者类对象作为参数传递时，都只是简单的把对象成员赋值给另一个对象，是不会调用构造函数的，但是当新对象声明周期结束时，是会调用析构函数的，如果我们在析构函数中执行了释放内存的动作，当原对象再调用析构时就会有问题。举个代码示例:</p>
<pre><code class="language-c++">#include &lt;stdio.h&gt;
#include &lt;string.h&gt;
#include &lt;stdlib.h&gt;

class TestClass
{
public:
    char* p;

    TestClass()
    {
        printf(&quot;do constructor\n&quot;);
        p = new char[20];
    }
    ~TestClass()
    {
        printf(&quot;do destructor\n&quot;);
        delete[] p;
    }

};

void test(TestClass s)
{
    printf(&quot;%d %d\n&quot;, s.i1, s.i2);
}

int main()
{
    TestClass s;
    test(s);

    return 0;
}
</code></pre>
<p>上面的程序会调用1次构造方法，2次析构方法，第二次再释放p所指的内存时就会触发异常。</p>
<p>解决之法1：就是写copy constructor或者重载= operator方法，在发生赋值操作时，给新变量申请一块新内存，这样在调用析构时，就不会释放掉原来的内存了。修改如下:</p>
<pre><code class="language-c++">class TestClass
{
public:
    char* p;

    TestClass()
    {
        printf(&quot;do constructor\n&quot;);
        p = new char[20];
    }
    TestClass(TestClass &amp; t)
    {
        printf(&quot;do copy constructor\n&quot;);
        p = new char[20];
        memcpy(p, t.p, 20);
    }
    ~TestClass()
    {
        printf(&quot;do destructor\n&quot;);
        delete[] p;
    }

};
</code></pre>
<p>解决方法2：就是不允许值传递，只能使用指针传递。把copy constructor和= operator设置为private,编译器就不会让值传递的代码编译通过了。</p>
<h1 id="私有不可访问吗"><a class="header" href="#私有不可访问吗">私有不可访问吗</a></h1>
<p>平常我们认为，我们无法访问到类的私有变量成员，但真的无法访问吗？作用域的控制只存在于编译期间，当运行时是没有私有，公有这种概念的，毕竟内存能不能访问，最起码是页级别的，操作系统是不能控制哪些字节大小的内存可以被程序访问。所以，只要我们能拿到成员的地址，我们就可以修改它。示例代码如下:</p>
<pre><code class="language-c++">#include &lt;iostream&gt;
using namespace std;
class MyTest
{
private:
    int a;
    int b;

public:
    void setB(int v)
    {
        b = v;
    }
    int getB()
    {
        return b;
    }
};

int main()
{
    MyTest t;
    t.setB(10);
    cout &lt;&lt; t.getB() &lt;&lt; endl;

    *((int*)&amp; t + 1) += 10;
    cout &lt;&lt; t.getB() &lt;&lt; endl;
    getchar();
    return 0;
}
</code></pre>
<p><code>*((int*)&amp; t + 1)</code>这句代码就是获取b的地址，把t的地址转换成int*,int*指针+1，就是加4字节，就是t对象中的b的地址，然后我们把这块内存的值加10，得到的结果就是20。平常我们无法访问私有变量是因为编译器不让我们的代码通过，是语法错误。我们避开这些语法，取出地址，直接操作是可以的。</p>
<h1 id="继承"><a class="header" href="#继承">继承</a></h1>
<ul>
<li>单一继承的内存模型</li>
<li>多继承的内存模型</li>
<li>菱形继承</li>
</ul>
<h2 id="单一继承的内存模型"><a class="header" href="#单一继承的内存模型">单一继承的内存模型</a></h2>
<p>我们不考虑带虚函数的继承方式，继承本质上就是在子类起始处声明了父类中的所有数据字段。<strong>重点:所有数据字段</strong>。普通的类中的方法不会影响类的大小，方法和普通的C函数没啥本质的区别，只是在调用方法之前编译器帮我们传递了this地址而已。示例程序:</p>
<pre><code class="language-c++">class A
{
public:
    int a;
};
class B: public A
{
public:
    int b;
};
</code></pre>
<p>用结构体表示上面的内存模型:</p>
<pre><code class="language-c++">struct A
{
    int a;
};
struct B
{
    struct A a;
    int b;
}
</code></pre>
<p>继承本质上就是把父类中的所有字段在子类中声明了出来，无论private还是public access level。子类中虽然有父类的private acess level的字段内存，但是不可直接访问，语法不支持。不过通过一些手段还是可以访问的。</p>
<h2 id="多继承的内存模型"><a class="header" href="#多继承的内存模型">多继承的内存模型</a></h2>
<p>单一继承方式理解了，多继承自然不在话下，就是把继承的父类一个一个的在子类中按照从左往右的继承循序声明一下。</p>
<pre><code class="language-c++">class A
{
public:
    int a;
};
class B
{
public:
    int b;
};
class C: public A, public B
{
public:
    int c;
};
</code></pre>
<p>用结构体表示上面的内存布局:</p>
<pre><code class="language-c++">struct A
{
    int a;
};
struct B
{
    int b;
};
struct C
{
    struct A a;
    struct B b;
    int c;
};
</code></pre>
<p>创建父类成员的顺序和声明的继承父类列表顺序有关。</p>
<h2 id="菱形继承"><a class="header" href="#菱形继承">菱形继承</a></h2>
<p>多重继承已经理解了，菱形继承自然不在话下。就不给出演示代码了，但是祖父类的成员我们不能直接访问，因为我们我们两个父类中都有这个属性，所以我们这个子类中就有两份这个属性，我们使用时要显式的指定使用哪个父类的成员:</p>
<pre><code>int main()
{
    C c;
    c.A::__base_field = 100;
    c.B::__base_field = 200;
    return 0;
}
</code></pre>
<p>我们清楚了继承的内存模型后，再去理解虚函数在继承中的内存模型就不是问题了。</p>
<h1 id="虚函数"><a class="header" href="#虚函数">虚函数</a></h1>
<ul>
<li>为什么引入虚函数</li>
<li>虚函数表指针</li>
<li>通过指针直接调用虚函数</li>
<li>虚函数的调用时机</li>
<li>析构方法为什么应该要写成虚函数形式</li>
<li>继承中的虚函数表内存模型</li>
<li>多重继承下的虚表内存模型</li>
</ul>
<p>一下环境均是x86模式。</p>
<h2 id="为什么引入虚函数"><a class="header" href="#为什么引入虚函数">为什么引入虚函数</a></h2>
<h2 id="虚函数表指针"><a class="header" href="#虚函数表指针">虚函数表指针</a></h2>
<p>如果类中存在virtual方法，则类的前面4字节(x86模式下，x64是8字节)存储的是虚函数表指针。这样通过对象地址就可以获取到虚函数表了，从而调用实际对象的虚函数，以此来实现多态。验证这个虚函数表指针的存在非常简单:</p>
<pre><code class="language-c++">class A
{
public:
    virtual void print() {};
};
int main()
{
    cout &lt;&lt; sizeof(A) &lt;&lt; endl;
}
</code></pre>
<p>平常如果没有这个虚函数，则输出是0。上面的输出结果是4，说明了当类中存在虚函数时，类中就会有一个虚函数表指针存在。当然，我们下面会进一步确认这个事情，毕竟现在只知道多了4字节莫名其妙的东西。</p>
<h2 id="通过指针直接调用虚函数"><a class="header" href="#通过指针直接调用虚函数">通过指针直接调用虚函数</a></h2>
<p>一方面我们可以通过反汇编的方式来验证虚函数表的存在。另一方面，我们可以通过虚函数表指针找到对应的虚函数地址，直接调用虚函数，来验证虚函数。使用第二种方式验证如下:</p>
<pre><code class="language-c++">class A
{
public:
    virtual void print() { cout &lt;&lt; &quot;A&quot; &lt;&lt; endl;};
};
int main()
{
    A a;
    int* a_ptr = (int*)&amp;a;
    void* print = *(void**)(*a_ptr);
    __asm {
        mov ecx, a_ptr;
        call print;
    };
    return 0;
}
</code></pre>
<h2 id="虚函数的调用时机"><a class="header" href="#虚函数的调用时机">虚函数的调用时机</a></h2>
<p>如果我们不是使用指针变量的方式调用虚函数，是不会去查询虚函数表，找到虚函数地址在调用的。而是直接生成对应方法的call指令，想想也是，如果不是指针，那变量一定是一个确定类型的，调用的方法肯定是这个类的方法，和普通的方法一样，根本不需要看其虚函数表是一个什么东西。</p>
<p>所以，如果要使用虚函数的调用机制，需要使用指针的方式调用，示例代码如下,并附带反汇编代码:</p>
<pre><code class="language-c++">#include &lt;iostream&gt;
using namespace std;

class Clerk
{
public:
    virtual void work()
    {
        cout &lt;&lt; &quot;i am a clerk&quot; &lt;&lt; endl;
    }
    virtual void finish()
    {
        cout &lt;&lt; &quot;i am a clerk, i finish it&quot; &lt;&lt; endl;
    }
};

int main()
{
    Clerk* clerk = new Clerk;
    clerk-&gt;work();
    clerk-&gt;finish();

    delete clerk;
    return 0;
}
</code></pre>
<pre><code>    clerk-&gt;work();
00B12927  mov         eax,dword ptr [ebp-8]  
00B1292A  mov         edx,dword ptr [eax]  
00B1292C  mov         esi,esp  
00B1292E  mov         ecx,dword ptr [ebp-8]  
00B12931  mov         eax,dword ptr [edx]  
00B12933  call        eax  
00B12935  cmp         esi,esp  
00B12937  call        00B112DA  
    clerk-&gt;finish();
00B1293C  mov         eax,dword ptr [ebp-8]  
00B1293F  mov         edx,dword ptr [eax]  
00B12941  mov         esi,esp  
00B12943  mov         ecx,dword ptr [ebp-8]  
00B12946  mov         eax,dword ptr [edx+4]  
00B12949  call        eax  
00B1294B  cmp         esi,esp  
00B1294D  call        00B112DA 
</code></pre>
<p>通过反汇编代码我们知道<code>[ebp-8]</code>就是clerk的数据，也就是我们new 出来的对象地址。调用虚函数时，会把虚函数表的地址存入edx中，通过虚函数在虚函数表中的偏移量得到虚函数地址，存入eax,并把对象地址存储ecx中，然后<code>call eax</code>的方式调用虚函数。</p>
<h2 id="析构方法为什么应该要写成虚函数形式"><a class="header" href="#析构方法为什么应该要写成虚函数形式">析构方法为什么应该要写成虚函数形式</a></h2>
<p>如果理解的上面的虚函数调用的方式，就不难理解为什么我们需要把析构方法写成虚函数的形式了。假设我们父类的析构方法不是虚函数形式的，考虑如下代码:</p>
<pre><code class="language-c++">Parent *p = new Child;
delete p;
</code></pre>
<p>上面p指向的是子类对象，讲道理释放内存的时候，应该调用具体子类的对象的析构方法，但事实时调用的是父类的析构方法，这就会造成子类中的资源无法释放。为什么会调用父类的析构方法呢？因为析构方法不是虚函数，虚函数表中就没有这个slot位置，通过虚函数表指针根本找不到析构方法，怎么会去调用具体类型的析构方法呢。所以，我们应该把父类的析构方法写成virtual的形式，走虚函数调用那一套，就能正确调用实际类型的析构方法了。</p>
<h2 id="继承中的虚函数内存模型"><a class="header" href="#继承中的虚函数内存模型">继承中的虚函数内存模型</a></h2>
<p>当类中中有虚函数时，编译器会自动帮我们在类的首部添加一个虚函数表指针成员，你可以把它看成一个没有名字的成员变量，其和我们写的成员变量一样，都会被子类继承。然后虚函数表子类也会复制父类的一份，如果子类override父类的虚函数，则对应的虚表位置上就变成了子类函数的地址。</p>
<pre><code class="language-c++">class A
{
    virtual void print(){}
};
class B:public A
{

};
</code></pre>
<p>用struct表示上面的内存模型:</p>
<pre><code class="language-c">struct A
{
    void** vt_ptr;
}
struct B
{
    struct A a;
}
</code></pre>
<h2 id="多重继承下的虚表内存模型"><a class="header" href="#多重继承下的虚表内存模型">多重继承下的虚表内存模型</a></h2>
<p>如果对多重继承机制比较了解的话，这个话题自然不是问题了，完全可以把虚函数表指针看成一个普通的变量，然后就知道多重继承下虚表相关的内存模型了。</p>
<p>为什么gcc/g++编译时，链接静态库会有顺序问题呢？</p>
<p>如libA.a依赖libB.a, main.c依赖这两个库，如果使用<code>gcc main.c -lA -lB</code>就是正确的，但是使用<code>gcc main.c -lB -lA</code>就是错误的，出现这种顺序问题的原因，简单来说是因为链接器如下的两个默认操作：</p>
<ul>
<li>每个库只扫描一次。</li>
<li>每个库中没有用到的符号会被丢弃。</li>
</ul>
<p>如果如果libB.a在前，ld拿到的符号使用完就丢弃了，再扫描libA.a时，发现其依赖一些外部的符号，然后把它们放到未定义符号的表中，这些符号再libB.a中定义的，但是不会再扫描libB了，所以就找不到这些符号实现了，就会报错。所以只需要记住一个重要的结论就是:<strong>把被依赖的库放到后边</strong>。</p>
<p>如果不知道静态库之间的依赖关系，报错时可以使用nm命令调试。</p>
<p>python</p>
<pre><code class="language-python">from  optparse import OptionParser
</code></pre>
<p>Deprecated since version 3.2: The optparse module is deprecated and will not be developed further; development will continue with the argparse module.</p>
<p>optparse是一个强大的，灵活的解析标准的Unix命令行参数的模块。标准的Unix命令行参数是什么格式呢?</p>
<p>一个选项有简写和全写两种方式来指定，如选项filename可以通过 <strong>-f hello.txt</strong> 或者 <strong>-fhello.txt</strong>和 <strong>--file=hello.txt</strong>或者 <strong>--file hello.txt</strong> 的形式</p>
<p>下面是一个示例</p>
<pre><code class="language-python">parser = OptionParser()
parser.add_option(&quot;-f&quot;, &quot;--file&quot;, dest=&quot;filename&quot;,
                  help=&quot;write report to FILE&quot;, type=&quot;string&quot;, metavar=&quot;FILE&quot;)
parser.add_option(&quot;-q&quot;, &quot;--quiet&quot;,
                  action=&quot;store_false&quot;, dest=&quot;verbose&quot;, default=True,
                  help=&quot;don't print status messages to stdout&quot;)
fake_args = ['-f', 'hello.txt', '-q', 'should no argument']
(options, args) = parser.parse_args(fake_args)
print(options)
print(args)
</code></pre>
<pre><code>{'filename': 'hello.txt', 'verbose': False}
['should no argument']
</code></pre>
<pre><code class="language-python">fake_args = ['--file=hello.txt', 'q']
(options, args) = parser.parse_args(fake_args)
print(options)
print(args)
</code></pre>
<pre><code>{'filename': 'hello.txt', 'verbose': True}
['q']
</code></pre>
<p>optparse使用上分为两步，首先通过add_option设置程序支持的选项参数，然后调用parse_args方法来解析传进来的参数，默认解析命令行参数,也就是sys.argv[1:] (sys.argv[0]指代是程序名本身，所以不需要解析)，不过可以以列表的方式指定解析的参数</p>
<h1 id="add_option"><a class="header" href="#add_option">add_option</a></h1>
<p>我们看一看add_option支持的关键字参数，首先就是dest,此参数指定了option对应的变量名，对应着你这个option的具体含义，如上所示的-f,--file参数想表达的就是filename,解析参数时，会用这个dest作为key来存储参数值。</p>
<p>action参数指定了解析到具体的参数时，应该做什么，optparse提供了一系列固定的操作如默认的store,表示碰到对应的参数，存下其值。还有常用的store_true和store_false,表示碰到对应的参数，存下true和false值。如上所示，-v的action时store_false,所以当我们传递-v参数时，解析到的值是false</p>
<p>其他的action：</p>
<ul>
<li>store_const  store a constant value</li>
<li>append  append this option’s argument to a list</li>
<li>count  increment a counter by one</li>
<li>callback call a specified function</li>
</ul>
<p>default指定了参数的默认值，当没有传递参数时，就使用默认值</p>
<p>type可以指定参数值的类型，支持integer,float,string三种类型，默认是string</p>
<p>help参数可以用来生成命令帮助输出信息，下面是一个例子</p>
<pre><code class="language-python">usage = &quot;usage: %prog [options] arg1 arg2&quot;
parser = OptionParser(usage=usage)
parser.add_option(&quot;-v&quot;, &quot;--verbose&quot;,
                  action=&quot;store_true&quot;, dest=&quot;verbose&quot;, default=True,
                  help=&quot;make lots of noise [default]&quot;)
parser.add_option(&quot;-q&quot;, &quot;--quiet&quot;,
                  action=&quot;store_false&quot;, dest=&quot;verbose&quot;,
                  help=&quot;be vewwy quiet (I'm hunting wabbits)&quot;)
parser.add_option(&quot;-f&quot;, &quot;--filename&quot;,
                  metavar=&quot;FILE&quot;, help=&quot;write output to FILE&quot;)
parser.add_option(&quot;-m&quot;, &quot;--mode&quot;,
                  default=&quot;intermediate&quot;,
                  help=&quot;interaction mode: novice, intermediate, &quot;
                       &quot;or expert [default: %default]&quot;)
</code></pre>
<pre><code>&lt;Option at 0x1949181f9c8: -m/--mode&gt;
</code></pre>
<p>上面为每一个option都指定了help参数，并且创建OptionParser时指定usage参数，当我们在命令行中指定 <strong>-h</strong> 或者 <strong>--help</strong> 时，就能输出帮助信息，我们也可以直接调用<code>parser.print_help()</code>来输出帮助信息。</p>
<pre><code class="language-python">parser.print_help()
</code></pre>
<pre><code>Usage: ipykernel_launcher.py [options] arg1 arg2

Options:
  -h, --help            show this help message and exit
  -v, --verbose         make lots of noise [default]
  -q, --quiet           be vewwy quiet (I'm hunting wabbits)
  -f FILE, --filename=FILE
                        write output to FILE
  -m MODE, --mode=MODE  interaction mode: novice, intermediate, or expert
                        [default: intermediate]
</code></pre>
<p>optparse 将会把 <strong>%prog</strong> 字符串转换成当前程序的名字。</p>
<h1 id="parse_args"><a class="header" href="#parse_args">parse_args</a></h1>
<p>option.parse_args的返回值是一个二元组，第一个是一个map 表示解析的参数值，key是参数名字，value是解析的值;第二个是一个列表存储的是无法解析的命令行参数，顺序和命令行传递的顺序一致。</p>
<p>linux编程</p>
<ul>
<li>gcc</li>
<li>g++</li>
<li>gdb</li>
<li>make</li>
<li>cmake</li>
<li>git</li>
<li>lrzsz</li>
</ul>
<p>fs</p>
<h2 id="目录操作相关"><a class="header" href="#目录操作相关">目录操作相关</a></h2>
<h2 id="mkdir"><a class="header" href="#mkdir">mkdir</a></h2>
<h2 id="opendir"><a class="header" href="#opendir">opendir</a></h2>
<h2 id="closedir"><a class="header" href="#closedir">closedir</a></h2>
<h2 id="readdir"><a class="header" href="#readdir">readdir</a></h2>
<h2 id="rewinddir"><a class="header" href="#rewinddir">rewinddir</a></h2>
<h2 id="rmdir"><a class="header" href="#rmdir">rmdir</a></h2>
<h1 id="文件操作相关"><a class="header" href="#文件操作相关">文件操作相关</a></h1>
<h2 id="open"><a class="header" href="#open">open</a></h2>
<h2 id="close"><a class="header" href="#close">close</a></h2>
<h2 id="write"><a class="header" href="#write">write</a></h2>
<h2 id="read"><a class="header" href="#read">read</a></h2>
<h2 id="lseek"><a class="header" href="#lseek">lseek</a></h2>
<h2 id="unlink"><a class="header" href="#unlink">unlink</a></h2>
<h1 id="getcwd和chdir"><a class="header" href="#getcwd和chdir">getcwd和chdir</a></h1>
<h1 id="stat"><a class="header" href="#stat">stat</a></h1>
<p>make</p>
<h1 id="makefile----todo"><a class="header" href="#makefile----todo">Makefile -- TODO</a></h1>
<p>更详细的教程参考<a href="https://seisman.github.io/how-to-write-makefile/">跟我一起写Makefile</a>.</p>
<h2 id="简介"><a class="header" href="#简介">简介</a></h2>
<p>在讲述这个makefile之前，还是让我们先来粗略地看一看makefile的规则。</p>
<pre><code>target ... : prerequisites ...
    command
    ...
    ...
</code></pre>
<p>makefile中3个概念：</p>
<ul>
<li><strong>target</strong> 可以是一个object file（目标文件），也可以是一个执行文件，还可以是一个标签（label）。对于标签这种特性，在后续的“伪目标”章节中会有叙述。</li>
<li><strong>prerequisites</strong> 生成该target所依赖的文件和/或target</li>
<li><strong>command</strong> 该target要执行的命令（任意的shell命令）
&gt;prerequisites中如果有一个以上的文件比target文件要新的话，command所定义的命令就会被执行。</li>
</ul>
<h2 id="make的工作方式"><a class="header" href="#make的工作方式">make的工作方式</a></h2>
<p>GNU的make工作时的执行步骤如下：（想来其它的make也是类似）</p>
<ol>
<li>读入所有的Makefile。</li>
<li>读入被include的其它Makefile。</li>
<li>初始化文件中的变量。</li>
<li>推导隐晦规则，并分析所有规则。</li>
<li>为所有的目标文件创建依赖关系链。</li>
<li>根据依赖关系，决定哪些目标要重新生成。</li>
<li>执行生成命令。</li>
</ol>
<p>1-5步为第一个阶段，6-7为第二个阶段。第一个阶段中，如果定义的变量被使用了，那么，make会把其展开在使用的位置。但make并不会完全马上展开，make使用的是懒加载，如果变量出现在依赖关系的规则中，那么仅当这条依赖被决定要使用了，变量才会在其内部展开。</p>
<h2 id="引用其它的makefile"><a class="header" href="#引用其它的makefile">引用其它的Makefile</a></h2>
<p>在Makefile使用 include 关键字可以把别的Makefile包含进来，这很像C语言的 #include ，被包含的文件会原模原样的放在当前文件的包含位置。 include 的语法是：</p>
<pre><code>include &lt;filename&gt;
</code></pre>
<p>filename 可以是当前操作系统Shell的文件模式（可以包含路径和通配符）。</p>
<p>在 include 前面可以有一些空字符，但是绝不能是 Tab 键开始。 include 和 <filename> 可以用一个或多个空格隔开。举个例子，你有这样几个Makefile： a.mk 、 b.mk 、 c.mk ，还有一个文件叫 foo.make ，以及一个变量 $(bar) ，其包含了 e.mk 和 f.mk ，那么，下面的语句：</p>
<pre><code>include foo.make *.mk $(bar)
</code></pre>
<p>等价于：</p>
<pre><code>include foo.make a.mk b.mk c.mk e.mk f.mk
</code></pre>
<p>如果有文件没有找到的话，make会生成一条警告信息，但不会马上出现致命错误。它会继续载入其它的文件，一旦完成makefile的读取，make会再重试这些没有找到，或是不能读取的文件，如果还是不行，make才会出现一条致命信息。如果你想让make不理那些无法读取的文件，而继续执行，你可以在include前加一个减号“-”。如：</p>
<pre><code>-include &lt;filename&gt;
</code></pre>
<h2 id="makefile-书写规则"><a class="header" href="#makefile-书写规则">Makefile 书写规则</a></h2>
<h3 id="使用通配符"><a class="header" href="#使用通配符">使用通配符</a></h3>
<p>如果我们想定义一系列比较类似的文件，我们很自然地就想起使用通配符。make支持三个通配符： * ， ? 和 ~ 。这是和Unix的B-Shell是相同的。</p>
<p>波浪号（ ~ ）字符在文件名中也有比较特殊的用途。如果是 ~/test ，这就表示当前用户的 $HOME 目录下的test目录。而 ~hchen/test 则表示用户hchen的宿主目录下的test 目录。（这些都是Unix下的小知识了，make也支持）而在Windows或是 MS-DOS下，用户没有宿主目录，那么波浪号所指的目录则根据环境变量“HOME”而定。
使用通配符的小case:</p>
<pre><code>objects := $(patsubst %.c,%.o,$(wildcard *.c))
</code></pre>
<h2 id="目标"><a class="header" href="#目标">目标</a></h2>
<h3 id="伪目标"><a class="header" href="#伪目标">伪目标</a></h3>
<p>“伪目标”并不是一个文件，只是一个标签，由于“伪目标”不是文件，所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显式地指明这个“目标”才能让其生效。当然，“伪目标”的取名不能和文件名重名，不然其就失去了“伪目标”的意义了。</p>
<p>当然，为了避免和文件重名的这种情况，我们可以使用一个特殊的标记“.PHONY”来显式地指明一个目标是“伪目标”，向make说明，不管是否有这个文件，这个目标就是“伪目标”。</p>
<pre><code>.PHONY : clean
</code></pre>
<p>只要有这个声明，不管是否有“clean”文件，要运行“clean”这个目标，只有“make clean”这样。于是整个过程可以这样写：</p>
<pre><code>.PHONY : clean
clean :
    -rm *.o temp
</code></pre>
<h3 id="多目标"><a class="header" href="#多目标">多目标</a></h3>
<p>Makefile的规则中的目标可以不止一个，其支持多目标，有可能我们的多个目标同时依赖于一个文件，并且其生成的命令大体类似。于是我们就能把其合并起来。当然，多个目标的生成规则的执行命令不是同一个，这可能会给我们带来麻烦，不过好在我们可以使用一个自动化变量 $@ （关于自动化变量，将在后面讲述），这个变量表示着目前规则中所有的目标的集合，这样说可能很抽象，还是看一个例子吧。</p>
<pre><code>bigoutput littleoutput : text.g
    generate text.g -$(subst output,,$@) &gt; $@
</code></pre>
<p>上述规则等价于：</p>
<pre><code>bigoutput : text.g
    generate text.g -big &gt; bigoutput
littleoutput : text.g
    generate text.g -little &gt; littleoutput
</code></pre>
<p>其中， <code>-$(subst output,,$@)</code> 中的 $ 表示执行一个Makefile的函数，函数名为subst，后面的为参数。关于函数，将在后面讲述。这里的这个函数是替换字符串的意思， $@ 表示目标的集合，就像一个数组， $@ 依次取出目标，并执于命令。</p>
<h3 id="静态模式"><a class="header" href="#静态模式">静态模式</a></h3>
<p>静态模式可以更加容易地定义多目标的规则，可以让我们的规则变得更加的有弹性和灵活。我们还是先来看一下语法：</p>
<pre><code>&lt;targets ...&gt; : &lt;target-pattern&gt; : &lt;prereq-patterns ...&gt;
    &lt;commands&gt;
    ...
</code></pre>
<p>targets定义了一系列的目标文件，可以有通配符。是目标的一个集合。</p>
<p>target-pattern是指明了targets的模式，也就是的目标集模式。</p>
<p>prereq-patterns是目标的依赖模式，它对target-pattern形成的模式再进行一次依赖目标的定义。</p>
<p>这样描述这三个东西，可能还是没有说清楚，还是举个例子来说明一下吧。如果我们的<target-pattern>定义成 %.o ，意思是我们的<target>;集合中都是以 .o 结尾的，而如果我们的<prereq-patterns>定义成 %.c ，意思是对<target-pattern>所形成的目标集进行二次定义，其计算方法是，取<target-pattern>模式中的 % （也就是去掉了 .o 这个结尾），并为其加上 .c 这个结尾，形成的新集合。</p>
<p>所以，我们的“目标模式”或是“依赖模式”中都应该有 % 这个字符，如果你的文件名中有 % 那么你可以使用反斜杠 \ 进行转义，来标明真实的 % 字符。</p>
<p>看一个例子：</p>
<pre><code>objects = foo.o bar.o

all: $(objects)

$(objects): %.o: %.c
    $(CC) -c $(CFLAGS) $&lt; -o $@
</code></pre>
<p>上面的例子中，指明了我们的目标从$object中获取， %.o 表明要所有以 .o 结尾的目标，也就是 foo.o bar.o ，也就是变量 $object 集合的模式，而依赖模式 %.c 则取模式 %.o 的 % ，也就是 foo bar ，并为其加下 .c 的后缀，于是，我们的依赖目标就是 foo.c bar.c 。而命令中的 $&lt; 和 $@ 则是自动化变量， $&lt; 表示第一个依赖文件， $@ 表示目标集（也就是“foo.o bar.o”）。于是，上面的规则展开后等价于下面的规则：</p>
<pre><code>foo.o : foo.c
    $(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
    $(CC) -c $(CFLAGS) bar.c -o bar.o
</code></pre>
<p>试想，如果我们的 %.o 有几百个，那么我们只要用这种很简单的“静态模式规则”就可以写完一堆规则，实在是太有效率了。“静态模式规则”的用法很灵活，如果用得好，那会是一个很强大的功能。再看一个例子：</p>
<pre><code>files = foo.elc bar.o lose.o

$(filter %.o,$(files)): %.o: %.c
    $(CC) -c $(CFLAGS) $&lt; -o $@
$(filter %.elc,$(files)): %.elc: %.el
    emacs -f batch-byte-compile $&lt;
</code></pre>
<p>$(filter %.o,$(files))表示调用Makefile的filter函数，过滤“$files”集，只要其中模式为“%.o”的内容。其它的内容，我就不用多说了吧。这个例子展示了Makefile中更大的弹性。</p>
<h2 id="使用变量"><a class="header" href="#使用变量">使用变量</a></h2>
<h2 id="隐含规则"><a class="header" href="#隐含规则">隐含规则</a></h2>
<h2 id="使用条件判断"><a class="header" href="#使用条件判断">使用条件判断</a></h2>
<h2 id="使用函数"><a class="header" href="#使用函数">使用函数</a></h2>
<p>other</p>
<ul>
<li>设置系统代理</li>
<li>其他软件的代理设置</li>
</ul>
<h1 id="设置系统代理"><a class="header" href="#设置系统代理">设置系统代理</a></h1>
<table><thead><tr><th>环境变量</th><th>描述</th><th>值示例</th></tr></thead><tbody>
<tr><td>http_proxy</td><td>为http变量设置代理；默认不填开头以http协议传输</td><td>10.0.0.51:8080<br/>user:pass@10.0.0.10:8080<br/>socks4://10.0.0.51:1080<br/>socks5://192.168.1.1:1080</td></tr>
<tr><td>https_proxy</td><td>为https变量设置代理</td><td>同上</td></tr>
<tr><td>ftp_proxy</td><td>为ftp变量设置代理；</td><td>同上</td></tr>
<tr><td>all_proxy</td><td>全部变量设置代理，设置了这个时候上面的不用设置</td><td>同上</td></tr>
<tr><td>no_proxy</td><td>无需代理的主机或域名； 可以使用通配符； 多个时使用“,”号分隔；</td><td><em>.aiezu.com,<br/>10.</em>.<em>.</em>,<br/>192.168.<em>.</em>,<br/>*.local,<br/>localhost,<br/>127.0.0.1</td></tr>
</tbody></table>
<h1 id="其他软件的代理设置"><a class="header" href="#其他软件的代理设置">其他软件的代理设置</a></h1>
<h2 id="yum设置"><a class="header" href="#yum设置">yum设置</a></h2>
<p>编辑/etc/yum.conf文件，按如下配置</p>
<pre><code>proxy=http://yourproxy:8080/      #匿名代理
proxy=http://username:password@yourproxy:8080/   #需验证代理
</code></pre>
<h1 id="负数的补码怎么得到"><a class="header" href="#负数的补码怎么得到">负数的补码怎么得到</a></h1>
<p>“反码加一”只是补码所具有的一个性质，不能被定义成补码。
补码重点在一个补字上，因为计算机内的数都是由二进制来表示的，而减法在数学上的本质其实也是加
法，只不过被理解为减去一个数，等于加上一个数在加法群中的逆（就是相反数），所以减法也可以借助
加法器来实现。
为了能够轻松实现加法，科学家们就想到了溢出这个方案。比如，为了保证加法器将两个互为相反数的值
加起来等于零，那就考虑让一个数加上一个非常大的数字，使得更高的位由于溢出（也就是说，因为寄存
器位数有限，更大的数字的更高位无法在计算机中被表示出来）而“变小”从而得到期望的结果。以4位加
法为例，如果把15当作-1，那么1+(-1)就变成了1+15=16。而16的二进制表示是10000，已经五位了，超
过四位了，所以高位的1舍弃，这个数在计算机里就成了0。这里的15所对应的二进制码就是-1的补码。
补码和位数紧密相关。负数的补码，是能够和其相反数相加通过溢出从而使计算机内计算结果变为0的二进
制码。假设寄存器是 位的，那么 的补码，应该是 的二进制编码。
比如，计算Java中byte类型的-3在计算机中的表示，那么将 代入 将得到
，所以11111101就是-3的byte类型补码也就是8位补码。
位补码“补充了”负数绝对值和 之间的“差距”，从而实现了将减法变成加法从而能够通过加法
器进行整数（包括正整数、负整数和0）的加减法运算。
那么为什么反码加一就能得到补码呢？这个首先需要明确的是， 这个不适用于一定位数下最小负数的情
形。然后，我们通过反码的定义来证明这一点。一个负数的反码是其相反数/绝对值按位取反。按位取反，
1变成0，0变成1。如果全1减去一个数就是按位取反，所以， 的反码就是 。
所以，看出来了吧，这反码和补码恰好差1，这是补码和反码的关系性质。</p>
<p>缓存</p>
<h1 id="缓存的穿透击穿雪崩"><a class="header" href="#缓存的穿透击穿雪崩">缓存的穿透，击穿，雪崩</a></h1>
<h2 id="基本概念"><a class="header" href="#基本概念">基本概念</a></h2>
<ul>
<li>缓存穿透：key对应的数据在数据源并不存在，每次针对此key的请求从缓存获取不到，请求都会到数据源，从而可能压垮数据源。穿透，穿透，就是直接透过了缓存服务器，缓存服务器像不存在的一样。比如用一个不存在的用户id获取用户信息，不论缓存还是数据库都没有，若利用此漏洞进行攻击可能压垮数据库。</li>
<li>缓存击穿：key对应的数据存在，但在过期了，此时若有大量并发请求过来，这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存，这个时候大并发的请求可能会瞬间把后端DB压垮。击穿，击穿，就是有这个key,但是还是没挡住请求，这就叫击穿。</li>
<li>缓存雪崩：当缓存服务器重启或者大量缓存集中在某一个时间段失效，这样在失效的时候，也会给后端系统(比如DB)带来很大压力。</li>
</ul>
<h2 id="解决方案"><a class="header" href="#解决方案">解决方案</a></h2>
<h3 id="缓存穿透"><a class="header" href="#缓存穿透">缓存穿透</a></h3>
<p>因为缓存和数据库中都没有这个请求过来的key, 每次都会把这个请求打到数据库上，给数据库造成压力。可以使用以下几种方案来解决穿透问题。</p>
<ol>
<li>做参数合法性校验。比如id不能为负数，数据库id都是从0开始的递增的，对于非法参数请求，直接返回，不做处理。这种方式是最好的，方便的。</li>
<li>缓存空数据。如果一个查询返回的数据为空（不管是数据不存在，还是系统故障），我们仍然把这个空结果进行缓存，但它的过期时间会很短，最长不超过五分钟。这种方式比较粗暴，也不太能挡住非法请求，比如id是每次都变的负数，还是次次把请求发到数据库上了。</li>
<li>布隆过滤器。布隆过滤器能确定哪些key是不存在的，哪些key是可能存在的。我们利用这个确定不存在的功能，过滤掉那些非法请求。(布隆过滤器是什么，就是一个大bitmap,其他的网上搜)</li>
</ol>
<h3 id="缓存击穿"><a class="header" href="#缓存击穿">缓存击穿</a></h3>
<p>非常热点的key在某个时间点上失效了，造成一瞬间大量请求到达数据库上。解决方法有一下几种：</p>
<ol>
<li>设置缓存超长过期时间。这是简单粗暴的解决方法。把缓存的过期时间设置n天或者永不过期，就可以解决击穿问题了。我们用的就是过期时间设置的是10天，但是配合LRU算法，把最久不用的key替换掉，来保证缓存数据集不会无限增大。</li>
<li>使用互斥锁。简单地来说，就是在缓存失效的时候（判断拿出来的值为空），不是立即去load db，而是先使用缓存工具的某些带成功操作返回值的操作（比如Redis的SETNX或者Memcache的ADD）去set一个mutex key，当操作返回成功时，再进行load db的操作并回设缓存；否则，就重试整个get缓存的方法。这样只会有一个请求去访问数据库。其他要获取这个数据的，只会等待，等到缓存更新完成。</li>
</ol>
<h3 id="缓存雪崩"><a class="header" href="#缓存雪崩">缓存雪崩</a></h3>
<p>与缓存击穿的区别是这里是多个key同一时间失效，造成大量请求到达数据库。缓存失效时的雪崩效应对底层系统的冲击非常可怕！和解决缓存击穿差不多吧。有以下解决方案:</p>
<ul>
<li>保证数据库读写线程数量。 大多数系统设计者考虑用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写，从而避免失效时大量的并发请求落到底层存储系统上。</li>
<li>缓存失效时间分散开，比如我们可以在原有的失效时间基础上增加一个随机值，比如1-5分钟随机，这样每一个缓存的过期时间的重复率就会降低，就很难引发集体失效的事件。</li>
</ul>
<p>reference</p>
<h1 id="memory-alignment-issue"><a class="header" href="#memory-alignment-issue">MEMORY ALIGNMENT ISSUE</a></h1>
<p><a href="http://junyelee.blogspot.com/2019/01/memory-alignment-issue.html">源地址</a></p>
<h2 id="alignment-fundamentals"><a class="header" href="#alignment-fundamentals">Alignment fundamentals</a></h2>
<p>When a computer reads from or writes to a memory address, it will do this in word sized chunks (for example, 4 byte (32-bit) chunks on the MPC8360). We'll call the size in which a processor accesses memory its memory access granularity.</p>
<p>Data alignment means putting the data at a memory offset equal to processor's memory access granularity, which increases the system's performance due to the way the CPU handles memory. Most CPUs can access only memory aligned addresses.</p>
<p>To illustrate how a processor's memory access granularity affects memory access, let's compare the following tasks:
read 4 bytes from address 0 into the processor's register
read 4 bytes from address 1 into the same register
It would happen on a processor with 4-byte memory access granularity,</p>
<p>read 4 bytes from the aligned address 0 needs single read instruction
read 4 bytes from the un-aligned address 1 into the same register needs 2 read instructions</p>
<p>The following show an example of some memory addresses and their alignment on different architectures.</p>
<h2 id="linux-unaligned-memory-accesses"><a class="header" href="#linux-unaligned-memory-accesses">Linux: UNALIGNED MEMORY ACCESSES</a></h2>
<p>Linux runs on a wide variety of architectures which have varying behavior when it comes to memory access.</p>
<p>The definition of an unaligned access</p>
<p>Unaligned memory accesses occur when you try to read N bytes of data starting from an address that is not evenly divisible by N (i.e. addr % N != 0).
For example, reading 4 bytes of data from address 0x10004 is fine, but reading 4 bytes of data from address 0x10005 would be an unaligned memory access.
The context here is at the machine code level: certain instructions read or write a number of bytes to or from memory (e.g. movb, movw, movl in x86 assembly).</p>
<p>Natural alignment</p>
<p>When accessing N bytes of memory, the base memory address must be evenly divisible by N, i.e.</p>
<pre><code> addr % N == 0
</code></pre>
<p>Why unaligned access is bad</p>
<p>a summary of the common scenarios is presented below:
Some architectures are able to perform unaligned memory accesses transparently, but there is usually a significant performance cost.
Some architectures raise processor exceptions when unaligned accesses happen. The exception handler is able to correct the unaligned access, at significant cost to performance.
Some architectures raise processor exceptions when unaligned accesses happen, but the exceptions do not contain enough information for the unaligned access to be corrected.
Some architectures are not capable of unaligned memory access, but will silently perform a different memory access to the one that was requested, resulting in a subtle code bug that is hard to detect!
It should be obvious from the above that if your code causes unaligned memory accesses to happen, your code will not work correctly on certain platforms and will cause performance problems on others.</p>
<p>Code that does not cause unaligned access
For example, take the following structure::</p>
<pre><code> struct foo {
  u16 field1;
  u32 field2;
  u8 field3;
 };
</code></pre>
<p>You'd be expecting field2 to be located at offset 2 bytes into the structure, i.e. address 0x10002, but that address is not evenly divisible by 4.
Fortunately, the compiler understands the alignment constraints, so in the above case it would insert 2 bytes of padding in between field1 and field2.
Therefore, for standard structure types you can always rely on the compiler to pad structures so that accesses to fields are suitably aligned.</p>
<p>Similarly, you can also rely on the compiler to align variables and function parameters to a naturally aligned scheme, based on the size of the type of the variable.</p>
<p>At this point, it should be clear that accessing a single byte (u8 or char) will never cause an unaligned access, because all memory addresses are evenly divisible by one.</p>
<p>The optimal layout of the above example is::</p>
<pre><code> struct foo {
  u32 field2;
  u16 field1;
  u8 field3;
 };
</code></pre>
<p>For a natural alignment scheme, the compiler would only have to add a single byte of padding at the end of the structure.
The compiler is aware of the alignment constraints and will generate extra instructions to perform the memory access in a way that does not cause unaligned access.</p>
<p>Code that causes unaligned access</p>
<p>The following function taken from include/linux/etherdevice.h is an optimized routine to compare two ethernet MAC addresses( 48-bits, 6 bytes ) for equality::</p>
<pre><code>  bool ether_addr_equal(const u8 *addr1, const u8 *addr2)
  {
  #ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
 u32 fold = ((*(const u32 *)addr1) ^ (*(const u32 *)addr2)) |
     ((*(const u16 *)(addr1 + 4)) ^ (*(const u16 *)(addr2 + 4)));

 return fold == 0;
  #else
 const u16 *a = (const u16 *)addr1;
 const u16 *b = (const u16 *)addr2;
 return ((a[0] ^ b[0]) | (a[1] ^ b[1]) | (a[2] ^ b[2])) == 0;
  #endif
  }
</code></pre>
<p>When CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS is not set, the hardware isn't able to access memory on arbitrary boundaries, the reference to a[0] causes 2 bytes (16 bits) to be read from memory starting at address addr1. This is understood to only work normally on 16-bit-aligned addresses.</p>
<p>Here is another example of some code that could cause unaligned accesses::</p>
<pre><code> void myfunc(u8 *data, u32 value)
 {
  [...]
  *((u32 *) data) = cpu_to_le32(value);
  [...]
 }
</code></pre>
<p>This code will cause unaligned accesses every time the data pointer parameter points to an address that is not evenly divisible by 4.</p>
<h3 id="avoiding-unaligned-accesses"><a class="header" href="#avoiding-unaligned-accesses">Avoiding unaligned accesses</a></h3>
<p>The easiest way to avoid unaligned access is to use the get_unaligned() and put_unaligned() macros provided by the &lt;asm/unaligned.h&gt;l header file. &quot;unaligned.h&quot; is an architecture-dependent file.
read from an address</p>
<p>u32 value = get_unaligned((u32 *) mem_addr);
write to an address</p>
<p>put_unaligned(value, (u32 *) mem_addr);
These macros work for memory accesses of any length (not just 32 bits as in the examples above). Be aware that when compared to standard access of aligned memory, using these macros to access unaligned memory can be costly in terms of performance.</p>
<p>How does the ARM Compiler support unaligned accesses?</p>
<p>Older ARM processors require data load and stores to be to/from architecturally aligned addresses. This means:</p>
<p>LDRB/STRB          - address must be byte aligned
LDRH/STRH          - address must be 2-byte aligned 
LDR/STR            - address must be 4-byte aligned</p>
<p>Load/store instructions that act on multiple registers, for example LDM, are considered as working with multiple word quantities, so these instructions also require 4-byte aligned addresses. An unaligned load is one where the address does not match the architectural alignment.</p>
<p>On older processors, such as ARM9 family based processors, an unaligned load had to be synthesized in software. Typically by doing a series of small accesses, and combining the results.</p>
<p>The ARMv6 architecture introduced the first hardware support for unaligned accesses. ARM11 and Cortex-A/R processors can deal with unaligned accesses in hardware, removing the need for software routines.</p>
<p>Support for unaligned accesses is limited to a sub-set of load/store instructions:</p>
<p>LDRB/LDRSB/STRB
LDRH/LDRSH/STRH
LDR/STR
Instructions which do NOT support unaligned accesses include:</p>
<p>LDM/STM
LDRD/STRD
Further, unaligned accesses are only allowed to regions marked as Normal memory type, and unaligned access support must be enabled by setting the SCTLR.A bit in the system control coprocessor. Attempts to perform unaligned accesses when not allowed will cause an alignment fault (data abort).</p>
<p>How hardware supports unaligned accesses</p>
<p>In many cases a processor cannot generate an unaligned access on its interfaces to the memory system. This applies to caches, TCMs and the system bus. In these situations, the processor will generate a series of accesses, to implement the unaligned access. This is similar to the software routines used for previous processors.</p>
<p>For example:</p>
<pre><code>    LDR r0, [0x8001]
</code></pre>
<p>Most modern ARM processors have 64-bit or 128-bit interfaces. In the above example a processor would typically read the 64-bit or 128-bit block contains bytes 0x8001, 0x8002, 0x8003 and 0x8004. Discarding the other bytes.</p>
<p>Another example:</p>
<pre><code>    LDR r0, [0x81FC]
</code></pre>
<p>The four bytes of this load span both a 64-bit and 128-bit boundary. So with either interface width, the processor would have to perform two reads.</p>
<p>In both of these examples it is possible to see that unaligned accesses require more work by the hardware. While more efficient than the software routines required by previous processors, it is still less efficient than aligned accesses.</p>
<h3 id="pointer-alignment-in-c"><a class="header" href="#pointer-alignment-in-c">Pointer alignment in C</a></h3>
<p>When compiling C, variables are by default architecturally aligned. A global of type int (or uint32_t) will be 4-byte aligned in memory. Similarly, a pointer of type int* is expected to contain a 4-byte aligned address.</p>
<p>Where this is not the case (or may not be the case) the variable or pointer MUST be marked with the __packed keyword. This is a warning to the compiler that this variable, structure or pointer is potentially unaligned. Technically, it reduces the expected alignment of the pointer to 1-byte. It is possible to set the alignment of all pointers to 1 by using the compiler command line switch --pointer_alignment=1, it which case the compiler will treat all accesses through pointers as though they may be unaligned.</p>
<h3 id="compiler-assumptions"><a class="header" href="#compiler-assumptions">Compiler assumptions</a></h3>
<p>When compiling for a ARMv6 or ARMv7-A/R processor, the ARM Compiler will assume that it can use unaligned accesses.</p>
<p>The --no_unaligned_access flag tells the compiler not to knowingly generate unaligned accesses. What is the significance of knowingly?</p>
<p>As mentioned above, a pointer should contain an address with correct alignment for the type.</p>
<pre><code>uint32_t* requires 4-byte alignment
uint16_t* requires 2-byte alignment
uint8_t* requires 1-byte alignment
</code></pre>
<p>For structures, the alignment is that of the most aligned member.</p>
<p>The compiler will generate code on the assumption that a pointer is correctly aligned. It does not add code to perform run-time checks. A pointer may contain an incorrectly aligned address for a number of reasons. A common cause is casting:</p>
<pre><code>uint8_t tmp;
uint32_t* pMyPointer = (uint32_t*)(&amp;tmp);
</code></pre>
<p>This code takes the address of an uint8_t variable, then casts that address as a uint32_t pointer. The compiler will still assume that pMyPointer is correctly aligned for a uint32_t pointer. The compiler might then generate code that results in an unaligned access unknowingly.</p>
<p>This can be avoided using the __packed qualifier:</p>
<p>__packed uint32_t* pMyPointer = (__packed uint32_t*)(&amp;tmp);</p>
<h2 id="code-generation"><a class="header" href="#code-generation">Code Generation</a></h2>
<p>When unaligned accesses are permitted, the compiler will continue to use instructions that support unaligned accesses (for example LDR and STR) for accesses through __packed pointers. However it will not use instructions such as LDM which do not support unaligned accesses.</p>
<p>When unaligned accesses are not permitted, either because the code is being built for an ARMv4 or ARMv5 processor, or because --no_unaligned_access is specified, the compiler will access __packed data by a performing a number of aligned accesses. Usually, this is done by calling a library function such as __aeabi_uread4().</p>
<h2 id="device-memory"><a class="header" href="#device-memory">Device Memory</a></h2>
<p>Address regions that are used to access peripherals rather than memory should be marked as Device memory. Depending upon the processor, this may be configured in the Memory Protection Unit (MPU) or the Memory Management Unit (MMU). Unaligned accesses are not permitted to these regions even when unaligned access support is enabled. If an unaligned access is attempted, the processor will take an abort.</p>
<p>The compiler does not have any information on which address ranges are device memory, and it is therefore the responsibility of the person writing the code to ensure that accesses to devices are aligned. In practice, this usually is the case simply because peripheral registers are at aligned addresses. It is also usual to access peripheral registers through volatile variables or pointers, which restricts the compiler to accessing the data with the size of access specified where possible. For further information on the restrictions imposed on volatile types, please see section 7.1.5 of the Procedure Call Standard for the ARM Architecture.</p>
<p>It is also necessary to avoid using C library functions such as memcpy() to access Device memory, as there is no guarantee of the type of accesses these functions will use. If it is necessary to copy a buffer of memory to a Device memory, you should provide a suitable copying routine and call this instead of memcpy().</p>
<h2 id="performance"><a class="header" href="#performance">Performance</a></h2>
<p>If code frequently accesses unaligned data, there may be a performance advantage in enabling unaligned accesses. However, the extent of this advantage will be dependent on many factors. Even though this support allows a single instruction to access unaligned data, this will often require multiple bus accesses to occur. Therefore the bus transactions performed by an unaligned access may be similar to those performed by the multiple instructions used when unaligned access support is disabled. The code without unaligned access support will have to perform various shift and logical operations, but on a multi-issue processor the execution time of these may be hidden by executing them in parallel with the memory accesses. There will also be a function call overhead when functions such as __aeabi_uread4() are used, though the impact of these may be reduced by branch prediction.</p>
<p>small_program</p>
<h1 id="程序模块"><a class="header" href="#程序模块">程序模块</a></h1>
<ul>
<li>一个简单的插件示例</li>
<li>一个简单的跨平台线程框架</li>
</ul>
<p>ssh</p>
<h1 id="ssh的高级使用方式"><a class="header" href="#ssh的高级使用方式">ssh的高级使用方式</a></h1>
<h2 id="ssh的基本使用"><a class="header" href="#ssh的基本使用">ssh的基本使用</a></h2>
<p>使用私钥登录</p>
<pre><code>ssh -i id_rsa root@192.168.56.1
</code></pre>
<p>ssh常用参数含义:</p>
<pre><code>-p port 指定远程sshd服务的端口
-f  后台运行ssh程序，做隧道转发时使用
-N  建立ssh连接后，不执行命令。（不会打开shell终端）和-f参数搭配使用
-D port 建立动态代理，指定本地的端口
-L [host1:][port1]:[host2:][port2] 通过本ssh连接建立一条host1:port1 -&gt; host2:port2的隧道
-C 传递的数据进行压缩，在网速不好的连接上比较有效。
</code></pre>
<h2 id="隧道的应用"><a class="header" href="#隧道的应用">隧道的应用</a></h2>
<p>隧道直白的说就是把远程的端口映射到本地的端口，发往本地端口的数据，都会通过ssh连接转发到远程端口上。ssh的隧道技术有以下的常见场景。</p>
<h3 id="1-多级跳转登录远程服务器"><a class="header" href="#1-多级跳转登录远程服务器">1. 多级跳转登录远程服务器</a></h3>
<p>场景如下:
我们本地的A机器要在C机器上进行操作，但是C是内网机器，只能通过跳板机B登录C。当执行scp这样的指令时，会非常的不方便，我们想直接登录C(直观上)，我们可以利用隧道技术，来完成这个目标。</p>
<pre><code>A(localhost)-&gt;B(10.10.0.2)-&gt;C(192.168.56.10)
</code></pre>
<p>首先建立一条B--&gt;C的隧道，在B机器上执行如下操作：</p>
<pre><code>root@10.10.0.2:~#ssh -fN -i id_rsa root@192.168.56.10 -L 6666:localhost:20
</code></pre>
<p>上面命令建立了一条B:6666 --&gt; C:20的隧道。发往B:6666端口的数据包都会转发到C:20上。</p>
<p>然后建立一条A--&gt;B:6666的隧道，在A机器上执行如下操作:</p>
<pre><code>root@hostA:~#ssh -fN -i id_rsa root@10.10.0.2 -L 6666:localhost:6666
</code></pre>
<p>上面命令建立了一条A:6666 --&gt; B:6666的隧道。发往A:6666的数据包都会转发到B:20上。</p>
<p>建立好隧道之后，我们直接从A登录到C:</p>
<pre><code>root@hostA:~#ssh -i id_rsa_C root@127.0.0.1 -p6666
</code></pre>
<p>注意，我们要使用登录到C的用户名和密钥文件。</p>
<p>我们分析以下A-&gt;B隧道怎么传递数据包的。A发送数据到A:6666, A:6666是ssh监听的，收到数据后，通过ssh连接把数据发给B:22,B:22收到数据之后，建立到B:6666的连接，把数据发送到B:6666。剩下的B到C的过程一样。
无论跳转多少个机器，我们都可以建立一个一个隧道，再把这些隧道连接起来，来达到直接操作目标机器的效果。</p>
<h3 id="2-端口映射数据加密"><a class="header" href="#2-端口映射数据加密">2. 端口映射，数据加密</a></h3>
<p>比如我们可以把翻墙服务器上的shadowsocks服务的端口映射到本地端口上，这样我们的数据都是通过ssh连接传输的。这样有两个好处：</p>
<ol>
<li>改变的流量特征值，不容易被封。因为在外界看来我们是ssh数据流量不是shadowsocks流量。</li>
<li>使用了ssh加密</li>
</ol>
<h2 id="内网穿透"><a class="header" href="#内网穿透">内网穿透</a></h2>
<p>-R参数的使用。前面建立的端口映射都是loacalhost-&gt;remote的，使用-R参数可以建立remote-&gt;localhost的端口映射。</p>
<pre><code>root@localhost:~#ssh -i id_rsa root@remote -R remote:6666:localhost:8888 -fN
</code></pre>
<p>这样就可以把发往remote:6666的数据，转发到localhost:8888上了。
看以下应用场景：
A是我的家里的机器，B是公网机器，C是公司的机器。A可以访问B,C也可以访问B,但是A和C不能互相访问。进行如下操作可以是A访问C。</p>
<p>在C上执行：</p>
<pre><code>root@C:~#ssh -i id_rsa root@B -R 1111:22 -fN
</code></pre>
<p>建立B:1111-&gt;C:22的端口映射，上面会让B的ssh监听1111端口。</p>
<p>在A上执行：</p>
<pre><code>root@A:~#ssh -i id_rsa root@B -L 6666:1111 -fN
</code></pre>
<p>建立A:6666-&gt;B:1111的端口映射。</p>
<p>现在A可以访问C了：</p>
<pre><code>root@A:~#ssh -i id_rsa_c root@localhost -p6666
</code></pre>
<p>只需要向本地6666端口发送连接请求就行了。</p>
<h2 id="动态代理"><a class="header" href="#动态代理">动态代理</a></h2>
<p>就是-D参数的作用。</p>
<pre><code>ssh -i id_rsa root@remote -fN -D 9999
</code></pre>
<p>通过-D参数建立了一个sock5/sock4的动态代理端口。这样我们就不用shadowsocks来搭建翻墙服务了，又安全又方便，只需要又sshd服务就够了。
话虽如此，但是自己搞的测试，发现网速太慢了，而且https连接变成了不安全连接，浏览器显示的。</p>
<h2 id="总结-2"><a class="header" href="#总结-2">总结</a></h2>
<p>无论是建立隧道还是动态代理，都是建立在一条ssh连接上的，数据都是走这条连接的。</p>
<h1 id="ssh私钥登录"><a class="header" href="#ssh私钥登录">ssh私钥登录</a></h1>
<p>密钥形式登录的原理是：利用密钥生成器制作一对密钥——一只公钥和一只私钥。将公钥添加到服务器的某个账户上，然后在客户端利用私钥即可完成认证并登录。这样一来，没有私钥，任何人都无法通过 SSH 暴力破解你的密码来远程登录到系统。此外，如果将公钥复制到其他账户甚至主机，利用私钥也可以登录。</p>
<h2 id="1-制作密钥对"><a class="header" href="#1-制作密钥对">1. 制作密钥对</a></h2>
<pre><code>[root@host ~]$ ssh-keygen  &lt;== 建立密钥对
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): &lt;== 按 Enter
Created directory '/root/.ssh'.
Enter passphrase (empty for no passphrase): &lt;== 输入密钥锁码，或直接按 Enter 留空
Enter same passphrase again: &lt;== 再输入一遍密钥锁码
Your identification has been saved in /root/.ssh/id_rsa. &lt;== 私钥
Your public key has been saved in /root/.ssh/id_rsa.pub. &lt;== 公钥
The key fingerprint is:
0f:d3:e7:1a:1c:bd:5c:03:f1:19:f1:22:df:9b:cc:08 root@host
</code></pre>
<p>密钥锁码在使用私钥时必须输入，这样就可以保护私钥不被盗用。当然，也可以留空，实现无密码登录。现在，在 root 用户的家目录中生成了一个 .ssh 的隐藏目录，内含两个密钥文件。id_rsa 为私钥，id_rsa.pub 为公钥。</p>
<h2 id="2-在服务器上安装公钥"><a class="header" href="#2-在服务器上安装公钥">2. 在服务器上安装公钥</a></h2>
<p>键入以下命令，在服务器上安装公钥：</p>
<pre><code>[root@host ~]$ cd .ssh
[root@host .ssh]$ cat id_rsa.pub &gt;&gt; authorized_keys
</code></pre>
<p>如此便完成了公钥的安装。为了确保连接成功，请保证以下文件权限正确：</p>
<pre><code>[root@host .ssh]$ chmod 600 authorized_keys
[root@host .ssh]$ chmod 700 ~/.ssh
</code></pre>
<h2 id="3-设置-ssh打开密钥登录功能"><a class="header" href="#3-设置-ssh打开密钥登录功能">3. 设置 SSH，打开密钥登录功能</a></h2>
<p>编辑 /etc/ssh/sshd_config 文件，进行如下设置：</p>
<pre><code>RSAAuthentication yes
PubkeyAuthentication yes
</code></pre>
<p>另外，请留意 root 用户能否通过 SSH 登录：</p>
<pre><code>PermitRootLogin yes
</code></pre>
<p>当你完成全部设置，并以密钥方式登录成功后，再禁用密码登录：</p>
<pre><code>PasswordAuthentication no
</code></pre>
<p>最后，重启 SSH 服务：</p>
<pre><code>[root@host .ssh]$ service sshd restart
</code></pre>
<h2 id="将私钥下载到客户端"><a class="header" href="#将私钥下载到客户端">将私钥下载到客户端</a></h2>
<p>使用私钥登录。</p>
<pre><code>ssh -i id_rsa root@remote_host
</code></pre>
<p>ubuntu</p>
<h1 id="ubuntu-application启动器文件"><a class="header" href="#ubuntu-application启动器文件">Ubuntu application启动器文件</a></h1>
<p>Ubuntu18.04 /usr/share/applications目录下的.desktop文件都是应用启动器文件，就像Windows下的开始菜单中的应用menu，描述了应用启动相关的信息。这个配置在KDE和GNOME桌面环境下都可以使用的。</p>
<p>下面介绍以下.desktop文件中的常用配置项，让我们创建自己的应用启动器。</p>
<table><thead><tr><th>配置项名</th><th>含义</th></tr></thead><tbody>
<tr><td>[Desktop Entry]</td><td>配置分组名</td></tr>
<tr><td>Name</td><td>应用名称,Name=Visual Studio Code</td></tr>
<tr><td>Comment</td><td>注释，Comment=Code Editing.</td></tr>
<tr><td>GenericName</td><td>描述。GenericName=Text Editor</td></tr>
<tr><td>Exec</td><td>执行的命令。Exec=/usr/share/code/code --no-sandbox --unity-launch %F</td></tr>
<tr><td>Icon</td><td>图标路径。Icon=/opt/SoftWare/idea-IU-172.4343.14/bin/idea.png</td></tr>
<tr><td>Type</td><td>启动器类型。Type=Applicatioin</td></tr>
<tr><td>Categories</td><td>应用的类型。Categories=Application;Programme;</td></tr>
<tr><td>MimeType</td><td>关联的文件类型。MimeType=text/plain;inode/directory;</td></tr>
<tr><td>Actions</td><td></td></tr>
<tr><td>Keywords</td><td></td></tr>
<tr><td>Encoding</td><td>编码， Encoding=UTF-8</td></tr>
<tr><td>X-Desktop-File-Install-Version</td><td></td></tr>
<tr><td>[Desktop Action new-empty-window]</td><td>配置分组名</td></tr>
<tr><td>Name</td><td></td></tr>
<tr><td>Exec</td><td></td></tr>
<tr><td>Icon</td><td></td></tr>
</tbody></table>
<p>说明： </p>
<p>其中 Exec 常用的参数有：%f %F %u %U </p>
<ul>
<li>%f：单个文件名，即使选择了多个文件。如果已选择的文件不在本地文件系统中（比如说在HTTP或者FTP上），这个文件将被作为一个临时文件复制到本地，％f将指向本地临时文件； </li>
<li>%F：文件列表。用于程序可以同时打开多个本地文件。每个文件以分割段的方式传递给执行程序。 </li>
<li>%u：单个URL。本地文件以文件URL或文件路径的方式传递。 </li>
<li>%U：URL列表。每个URL以分割段的方式传递给执行程序。本地文件以文件URL或文件路径的方式传递。</li>
</ul>
<p>修改启动器文件权限:
<code>chmod 644 code.desktop</code></p>
<p>路径:/usr/share/applications/</p>
<h2 id="相关文档"><a class="header" href="#相关文档">相关文档</a></h2>
<p>更多的配置详细或者详情参考官方文档。</p>
<p><a href="https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html">官方文档</a></p>
<p>x</p>
<h1 id="linux下的x是什么"><a class="header" href="#linux下的x是什么">Linux下的X是什么</a></h1>
<h2 id="linux本身没有图形界面"><a class="header" href="#linux本身没有图形界面">linux本身没有图形界面</a></h2>
<blockquote>
<p>linux现在的图形界面的实现只是linux下的应用程序实现的：
图形界面并不是linux的一部分，linux只是一个基于命令行的操作系统，linux和Xfree的关系就相当于当年的DOS和WINDOWS 3.0 一样，windows 3.0不是独立的操作系统，它只是DOS的扩充，是DOS下的应用程序级别的系统，不是独立的操作系统，同样XFree只是 linux下的一个应用程序而已。不是系统的一部分，但是X的存在可以方便用户使用电脑。WINDOWS 95及以后的版本就不一样了，他们的图形界面是操作系统的一部分，图形界面在系统内核中就实现了，没有了图形界面windows就不成为windows 了，但linux却不一样，没有图形界面linux还是linux，很多装linux的WEB服务器就根本不装X服务器。这也WINDOWS和linux 的重要区别之一。 </p>
</blockquote>
<h2 id="x是协议不是具体的某个软件"><a class="header" href="#x是协议不是具体的某个软件">X是协议，不是具体的某个软件</a></h2>
<p>　　X是协议，就像HTTP协议，IP协议一样。这个概念 很多初学者甚至学习 LINUX有一定时间的人都混淆，一个基于X的应用程序需要运行并显示内容时他就联接到X服务器，开始用X协议和服务器交谈。比如一个X应用程序要在屏幕 上输出一个圆，X应用程序只负责告诉X服务器在屏幕的什么地方用什么颜色画一个多大的圆，而具体的&quot;画&quot;的动作，比如这个圆如何生成，用什么显卡的驱动程 序去指挥显卡完成等等工作是由X服务器来完成的。X服务器还负责捕捉键盘和鼠标的动作，假设X服务器捕捉到鼠标的左键被按下了，他就告诉X应用程序：亲爱 的应用程序先生，我发现鼠标被按下了，您有什么指示吗？协议是需要具体的软件来实现的，这就是下面我要讲的: </p>
<h2 id="x和xfree86的关系"><a class="header" href="#x和xfree86的关系">X和XFree86的关系</a></h2>
<p>有了协议就需要具体的软件来实现这个协议。Xfree86就是这样一个去根据法规实现协议的&quot;交警&quot;。实现X协议的软件也并不只有 XFree86，XFree86只是实现X协议的一个免费X服务器软件。商业上常用MOTIF，现在还有XORG，还有很多很小的由爱好者写的小的X服务 器软件。甚至可以在WINDOWS上有X服务器运行，这样你可以在linux系统上运行一个X应用程序然后在另一台windows系统上显示。</p>
<p>在LINUX上最常用的是XFree86（现在的linux发行版都用Xorg了）。苹果电脑的图形界面用的也是X协议，而且被认为是做的最好的X协 议图形界面，并且他对X协议的实施是做在系统内核里的，所以性能明显好很多，这就是为什么很多大型三维图形设计软件都是在苹果平台上的原因。</p>
<p>既然是协议，那么肯定和平台无关的，因此可以让X应用程序显示在任何装有X服务器的远程计算机上。甚至显示在装有Xnest服务器的windows系 统上。只不过在大多数情况下，X应用程序都用本机的X服务器，然后X服务器将显示结果输出到本机的显示器。这也是很多人不理解X是一种协议的原因。</p>
<h2 id="x和x11r6又是什么关系"><a class="header" href="#x和x11r6又是什么关系">X和X11R6又是什么关系？</a></h2>
<p>不知道初学者有没有注意到/usr/X11R6这个目录，这是XFree的默认安装目录。X11R6 实际上是X Protocol version 11 Release 6（X协议第11版第六次发行）的意思。不同版本的X协议是不能通信的，不过不用担心，现在的X服务器软件和X应用程序都遵循X11R6。另外XFree86 3.3.6 XFree86 4.3.6等这些版本是实现X协议的软件XFree86的版本号。</p>
<h2 id="x服务器和wmwindow-manager-窗口管理器之间是什么关系"><a class="header" href="#x服务器和wmwindow-manager-窗口管理器之间是什么关系">X服务器和WM(window manager 窗口管理器)之间是什么关系。</a></h2>
<p>平时大家起动图形界面是怎么启动的呢？ 如果一开始就进入图形界面，可打开一个XTERM输入init 3，就可以安全的回到字符界面。</p>
<p>先输入以下命令: # startx 起动图形界面，你看到的是一个和平时使用一样的完整的图形界面操作环境。你可以最大化，最小化，移动，关闭窗口等</p>
<p>按ctrl+alt+backspace反回字符界面。输入：#xinit，再次启动图形界面，你看到一个XTERM。而且不能移动。但是你可以在这 个XTERM中输入命令打开X应用程序，如果我输入：#mozilla 打开浏览器，你看到的浏览器和平时有什么不同吗?他在屏幕中间，不能移动，不能最小化，不能最大化，没有边框。</p>
<p>为什么同样一个X应用程序会有这样的不同呢？因为我们用startx起动图形界面的时候同时也启动了一个WM（即窗口管理器），如果你用KDE就起动 了 KDE，如果你用GNOME就起动了GNOME。但是你用xinit起动图形界面的时候却没有起动WM。现在你明白窗口管理器的作用了吗?他的作用就是最 大化，最小化，移动，关闭窗口等。而这些不是X服务器来负责完成的。</p>
<p>如果你用xinit起动图形界面并在xterm中输入twm，看看会有什么？xterm被加上了一个边框，你可以通过这个边框移动，最大化，最小化这 个xterm，twm就是XFree86中自带的窗口管理器，是一个比较简陋的最简单的窗口管理器，但是他具有窗口管理器的全部特征。如果你不输入twm 而输入gnome-session就可以起动GNOME 或者输入startkde起动KDE。 </p>
<h2 id="关于kde和gnome"><a class="header" href="#关于kde和gnome">关于KDE和GNOME</a></h2>
<p>KDE和GNOME是LINUX里最常用的图形界面操作环境，他们不仅仅是一个窗口管理器那么简单，kDE是K Desktop Environment的缩写。他不仅是一个窗口管理器，还有很多配套的应用软件和方便使用的桌面环境，比如任务栏，开始菜单，桌面图标等等。 
GNOME是GNU Network Object Model Environment的缩写。和KDE一样，也是一个功能强大的综合环境。 
另外在其它UNIX系统中，常常使用CDE作为这样一个环境。 
其它的小型窗口管理器有: window maker，after step，blackbox，fvwm，fvwm2，等等都是常用的优秀窗口管理器。REDHAT9中有window maker 但是默认不安装，大家可以装来试试。只要xinit再wmaker &amp; 就可以用windowmaker了。</p>
<h2 id="linux图形界面层次关系总结"><a class="header" href="#linux图形界面层次关系总结">linux图形界面层次关系总结</a></h2>
<p>linux本身--&gt;X服务器&lt;-[通过X协议交谈]-&gt;窗口管理器（综合桌面环境）--&gt;X应用程序。</p>
<h1 id="win10安装vcxsrv"><a class="header" href="#win10安装vcxsrv">win10安装VCXSRV</a></h1>
<p>VCXSRV是一款windows平台的Xserver软件，还有其他的XServer软件如Xming等。</p>
<h2 id="允许远程连接"><a class="header" href="#允许远程连接">允许远程连接</a></h2>
<p>默认启动的XLaunch是不允许远程Xclient连接的，只能本地连接(WSL的Xclient程序)。
我们可以设置Extra Settings-&gt;Disable access control,打上对勾后，就能运行远程连接了。</p>
<h2 id="验证xserver"><a class="header" href="#验证xserver">验证XServer</a></h2>
<p>在WSL中执行如下命令:</p>
<pre><code>export DISPLAY=127.0.0.1:0.0
apt install x11-apps -y &amp;&amp; xeyes
</code></pre>
<p>DISPLAY环境变量设置了XServer的地址和displayId.DISPALY变量格式为[XServerAddress]:{DisplayId}.{Num}。如果Address为空，则通过域套接字连接到本地的XServer。DisplayId与XServer启动时指定的DisplayId相对应，这个DisplayId加上6000(6000是默认端口号)为X Server的监听端口号。Num为Screen Number，默认为0.</p>
<p>如果win10屏幕上出现了一个有大大眼睛的窗口，就说明我们的XServer启动成功了。</p>
<h2 id="设置高清显示"><a class="header" href="#设置高清显示">设置高清显示</a></h2>
<p>安装好默认启动的xlaunch绘制的图像有点模糊相对于原生的windows窗口。因为现在很多电脑都有HiDPI，需要对VCXSRV做点设置不然字体会发虚。首先找到软件的安装路径，比如C:\Program Files\VcXsrv，然后对两个可执行文件vcxsrv.exe和xlaunch.exe执行以下操作：
右键点击可执行文件–&gt;属性–&gt;兼容性–&gt;更改高DPI设置–&gt;替代高DPI缩放行为。重启软件，发现绘制的图像就比原来清楚多了，当然了和原生的还是没法比。</p>
<h1 id="x-application配置"><a class="header" href="#x-application配置">X Application配置</a></h1>
<h2 id="xterm配置"><a class="header" href="#xterm配置">XTerm配置</a></h2>
<p>XTerm最重要的是字体的配置，默认的XTerm字体非常小，而且不能显示中文。</p>
<p>首先在系统中找到中文字体。可以使用gnome-font-viewer命令来预览系统中所有的字体，如果没有这个命令可能需要安装以下，或者不想预览字体风格。</p>
<p>fc-list命令会打印出所有的字体文件和字体名称，我们需要在XTerm配置中填写对应的字体名成。</p>
<pre><code>root@jackson-desktop:/etc/X11/app-defaults# fc-list | grep CN
/usr/share/fonts/truetype/arphic/ukai.ttc: AR PL UKai CN:style=Book
/usr/share/fonts/truetype/arphic/uming.ttc: AR PL UMing CN:style=Light
</code></pre>
<p><strong>AR PL UKai CN:style=Book</strong> 就是字体名。这就是我用的中文字体。</p>
<p>下面是一份XTerm配置，配置在了/etc/X11/application-defaults/XTerm中，这样所有的用户都能使用这个配置，保存就能生效。也可以配置在${HOME}/.Xresources文件中，只对当前用户生效，默认情况下没有这个文件，需要创建一个新文件。保存内容后，然后执行命令： xrdb -load ~/.Xresources，使配置生效。</p>
<pre><code>XTerm*locale: true
XTerm*utf8Title: true
XTerm*fontMenu*fontdefault*Label: Default
!英文字体
XTerm*faceName: Courier 10 Pitch:antialias=True:pixelsize=18
!中文字体
!XTerm*faceNameDoublesize: Microsoft YaHei:antialias=True:pixelsize=12
XTerm*faceNameDoublesize: AR PL UKai CN:style=Book:antialias=True:pixelsize=18
!中文输入
XTerm*inputMethod: ibus
XTerm*faceSize: 20
XTerm*faceSize1: 20
XTerm*faceSize2: 20
XTerm*faceSize3: 20
XTerm*faceSize4: 20
XTerm*faceSize5: 20
XTerm*faceSize6: 20
XTerm.cjkWidth:true
XTerm*background: #C7EDCC
XTerm*foreground: #000000
XTerm*scrollBar: true
XTerm*rightScrollBar: true
XTerm*jumpScroll: true
XTerm*SaveLines: 1000

XTerm*VT100.Translations: #override \n\
Ctrl &lt;KeyPress&gt; V: insert-selection(CLIPBOARD,PRIMARY,CUT_BUFFER0) \n\
&lt;BtnUp&gt;: select-end(CLIPBOARD,PRIMARY,CUT_BUFFER0) \n\
Ctrl &lt;KeyPress&gt; P: print() \n
</code></pre>
<h2 id="配置语法"><a class="header" href="#配置语法">配置语法</a></h2>
<p><a href="https://wiki.archlinux.org/index.php/x_resources">原文地址</a></p>
<p>该文件由一行一行的配置组成，每一行的语法格式为：name.Class.resource: value，比如：xscreensaver.Dialog.headingFont: -<em>-fixed-bold-r-</em>-<em>-</em>-100-<em>-</em>-<em>-</em>-iso8859-1</p>
<p>name：应用程序的名称，如xterm，xpdf等，可以通过命令程序的-name属性指定name。</p>
<p>Class：用于将资源分组在一起的分类。Class名通常是大写的。</p>
<p>resource：要更改其值的资源的名称。 资源通常是使用大写拼接的小写字母，类似于驼峰命名。</p>
<p>value：实际值。可以使整数、布尔（true/false, yes/no, on/off）、字符串（white、#ffffff、/usr/bin/firefox）。</p>
<p>delimiters： 冒号（:)用于将resource与value分开。点（.）用于分隔name、Class、resource。</p>
<p>Wildcard matching：
星号（<em>）可以作为通配符使用，可以很容易地编写一个可以应用于许多不同的应用程序或元素的规则。使用前面的示例，如果要将相同的字体应用于包含resource为headingFont且Class名为Dialog的所有程序（而不仅仅是XScreenSaver），则可以这样写：<em>Dialog.headingFont: -</em>-fixed-bold-r-</em>-<em>-</em>-100-<em>-</em>-<em>-</em>-iso8859-1
如果要为所有包含resource为headingFont的应用程序使用相同的规则，可以定义：<em>headingFont: -</em>-fixed-bold-r-<em>-</em>-<em>-100-</em>-<em>-</em>-*-iso8859-1
Commenting：在行首使用叹号(!)进行注释；</p>
<p>Include files：
对于每个应用程序的配置，可以写在不同的配置文件中，然后在.Xresources文件中进行引用：</p>
<pre><code>#include &quot;.Xresources.d/xterm&quot;
#include &quot;.Xresources.d/rxvt-unicode&quot;
#include &quot;.Xresources.d/fonts&quot;
#include &quot;.Xresources.d/xscreensaver&quot;
</code></pre>
<p>注意：使用此功能需要安装C的预处理器，比如GNU CPP。</p>
<h1 id="startx与xinit"><a class="header" href="#startx与xinit">startx与xinit</a></h1>
<ul>
<li>X-server管理鼠标、键盘、显卡、显示器</li>
<li>X-client处理程序的运行</li>
<li>WM管理窗口：移动、变型、关闭、装饰......</li>
<li>DE桌面环境：窗口管理器+面板+文件管理器+配套软件......</li>
<li>发行版：特定内核+特定桌面环境+特定包管理+特定软件源......</li>
</ul>
<p>互动关系：</p>
<ul>
<li>X-server(鼠标、键盘)==&gt;X-client解释指令==&gt;X-server(显卡、显示器)</li>
<li>X-server接收指令==&gt;X-client解释指令==&gt;X-server落实指令</li>
<li>X-server&lt;===&gt;WM&lt;===&gt;X-client</li>
</ul>
<p>startx用法：startx [ [ client ] options ... ] [ -- [ server ] [ display ] options ... ]</p>
<p>startx三种启动方式：</p>
<ol>
<li>指定client和server， 例如：startx /usr/bin/xclock -- /usr/bin/X :0</li>
<li>$HOME/.xserverrc启动server,$HOME/.xinitrc启动的client,</li>
<li>startx，启动DE</li>
</ol>
<h2 id="xinit"><a class="header" href="#xinit">xinit</a></h2>
<p>startx只是一个bash脚本，干活的是xinit <strong>(在普通user下运行xinit失败，可能权限问题吧，但是可以运行startx成功)</strong>。
xinit先启动先启动X服务器，再启动基于X的应用程序。</p>
<p>当我们安装了Ubuntu后，默认就已经安装了xinit，它位于/usr/bin下。xinit是一个二进制文件，并非是一个脚本。它的主要功能是启动一个X服务器，同时启动一个基于X的应用程序。</p>
<p>xinit的用法为：xinit [[client] options ] [-- [server] [display] options]。其中client用于指定一个基于X的应用程序，client后面的options是传给这个应用程序的参数，server是用于指定启动哪个X服务器，一般为/usr/bin/X或/usr/bin/Xorg，display用于指定display number，一般为0，表示第一个display，option为传给server的参数。</p>
<p>下面是几个关于xinit应用的例子：</p>
<ol>
<li>xinit /usr/bin/xclock -- /usr/bin/X :0</li>
</ol>
<p>该例子将启动X server， 同时将会启动xclock。请注意指定client或server时，需要用绝对路径，否则xinit将因无法区别是传给xterm或server的参数还是指定的client或server而直接当成是参数处理。</p>
<ol start="2">
<li>在HOME下新建.xinitrc文件，并加入以下几行：</li>
</ol>
<pre><code>xsetroot -solid gray &amp;
xclock -g 50x50-0+0 -bw 0 &amp;
xterm -g 80x24+0+0 &amp;
xterm -g 80x24+0-0 &amp;
twm
</code></pre>
<p>当xinit启动时，它会先启动X server，然后启动一个clock，两个xterm，最后启动窗口管理器twm。</p>
<p>请注意：最后一个命令不能后台运行，否则所有命令都后台运行的话xinit就会返回退出，同样的，除最后一个命令外都必须后台运行，否则后面的命令将只有在该命令退出后才能运行。</p>
<p>看到这里，眼尖的人或许早以看出xinit的功能完全可以由脚本来实现，例如要启动X Server 和一个xterm，就像xinit默认启动的那样，只需要在新建一个脚本或在rc.local中加入：</p>
<pre><code class="language-sh">X&amp;
export DISPLAY=:0.0
xterm
</code></pre>
<p>这个实现完全正确，然而却并没有完全实现xinit所具有的功能，xinit所有的一项功能就是当最后一个启动的client（如上面第二个例子中的twm窗口管理器）退出后，X服务器也会退出。而我们的脚本实现中当我们退出xterm后并不会退出X server。</p>
<h2 id="startx"><a class="header" href="#startx">startx</a></h2>
<p>startx是一个脚本，位于/usr/bin下。当我们在终端下想启动图形界面时，我们都会通过输入startx来实现，该命令可以启动一个Xserver，而且可以启动一个漂亮的图形界面（Ubuntu下，我装的是gnome）。</p>
<p>Startx的用法和xinit的基本一样：startx [ [ client ] options ... ] [ -- [ server ] options ... ]。为什么呢？这是因为startx其实就是一个脚本，它启动X server就是通过调用xinit命令实现的，startx的参数将全部传给xinit。因此，这些参数的意义和xinit的参数是一样的。</p>
<p>下面是两个关于startx命令的简单例子：</p>
<ol>
<li><code>startx -- -depth 16</code></li>
</ol>
<p>该例子主要是以16位色启动X 服务器。</p>
<ol start="2">
<li><code>startx -- -dpi 100</code>
该例子主要是以100的dpi启动X 服务器。</li>
</ol>
<h2 id="总结-3"><a class="header" href="#总结-3">总结</a></h2>
<p>由以上对startx脚本的分析，我们可以知道：startx将会先解析用户的参数，如果该用户指定了该参数（即解析结果不为空），那么startx就会以该参数来启动xinit，否则就会解析（与其说是解析，还不如说是执行）$HOME目录下的rc文件，如果该文件不存在，就会解析系统目录下（/etc/X11/xinit/）的rc文件，如果这个文件也不存在，那startx就将以默认的client（xterm）和server（/usr/bin/X）为参数来启动xinit。</p>
<p>通过以上对startx脚本的分析，我们知道了startx的基本的启动流程，但是到目前为止，我们还不知道仅仅在终端输入startx是怎么样启动gnome那漂亮的桌面的，下面我们来看一下其启动过程。</p>
<p>由对startx脚本的分析，我们可以知道startx主要有三种启动方式：</p>
<ol>
<li>一种是自己指定要启动的client和server， 例如：startx /usr/bin/xclock -- /usr/bin/X :0；</li>
<li>一种是通过在$HOME下新建.xinitrc文件来指定要启动的多个client和.xserverrc来指定要启动的server（注意：这两个文件本来是不存在的）；</li>
<li>还有一种是直接输入startx而不指定参数，这也就是我们启动gnome桌面的方法。这里我们主要介绍最后一种启动方法。</li>
</ol>
<p>在这三种启动方法中，我们可以知道，startx脚本会先去看系统目录（/etc/X11/xinit/）下的rc文件是否存在，如果不存在就会用默认的xterm和/usr/bin/X来启动xinit。显然，startx启动的不是xterm，而是gnome桌面，因此gnome的启动是通过系统文件/etc/X11/xinit/xinitrc来指定的。</p>
<p>startx的默认启动过程为：startx调用并将系统文件/etc/X11/xinit/xinitrc和/etc/X11/xinit/xserverrc 作为参数传给xinit，xinit就会先执行系统文件/etc/X11/xinit/xserverrc以启动X Server，然后执行/etc/X11/xinit/xinitrc，而xinitrc则会执行脚本/etc/X11/Xsession，而Xsession则会按顺序调用执行/etc/X11/Xsession.d目录下的文件，从而最终调用了gnome-session这个用于启动GNOME桌面环境的程序。</p>
<p>我们如何修改默认启动的XClient程序呢，通过上面的分析我们，无参数直接运行startx命令会执行x-session-manager,x-session-manager是/etc/alternatives/x-session-manager的软连接，而/etc/alternatives/x-session-manager是/usr/bin/gnome-session的软连接，我们只需要修改这个软连接就行了，比如：</p>
<pre><code>ln -s /usr/local/bin/fvwm  x-session-manager
</code></pre>
<p>这样运行startx，就会启动fvwm XClient了。</p>
<ul>
<li>QPS TPS RT</li>
<li>QoS</li>
</ul>
<h1 id="qps"><a class="header" href="#qps">QPS</a></h1>
<ul>
<li>QPS Query Per Second 每秒查询率</li>
<li>TPS Throughput Per Second 吞吐量是指系统在单位时间内处理请求的数量</li>
<li>并发用户数 并发用户数是指系统可以同时承载的正常使用系统功能的用户的数量</li>
<li>RT Response Time 响应时间 响应时间是指系统对请求作出响应的时间</li>
<li>QoS Quality of Service 服务质量 从传统意义上来讲，无非就是传输的带宽、传送的时延、数据的丢包率等，而提高服务质量无非也就是保证传输的带宽，降低传送的时延，降低数据的丢包率以及时延抖动等</li>
</ul>
<p>网络</p>
<h2 id="netstat中各个状态"><a class="header" href="#netstat中各个状态">netstat中各个状态</a></h2>
<ul>
<li>
<p>CLOSED 
初始（无连接）状态。</p>
</li>
<li>
<p>LISTEN 
侦听状态，等待远程机器的连接请求。</p>
</li>
<li>
<p>SYN_SEND 
在TCP三次握手期间，主动连接端发送了SYN包后，进入SYN_SEND状态，等待对方的ACK包。</p>
</li>
<li>
<p>SYN_RECV 
在TCP三次握手期间，主动连接端收到SYN包后，进入SYN_RECV状态。</p>
</li>
<li>
<p>ESTABLISHED 
完成TCP三次握手后，主动连接端进入ESTABLISHED状态。此时，TCP连接已经建立，可以进行通信。</p>
</li>
<li>
<p>FIN_WAIT_1 
在TCP四次挥手时，主动关闭端发送FIN包后，进入FIN_WAIT_1状态。</p>
</li>
<li>
<p>FIN_WAIT_2 
在TCP四次挥手时，主动关闭端收到ACK包后，进入FIN_WAIT_2状态。</p>
</li>
<li>
<p>TIME_WAIT 
在TCP四次挥手时，主动关闭端发送了ACK包之后，进入TIME_WAIT状态，等待最多MSL时间，让被动关闭端收到ACK包。</p>
</li>
<li>
<p>CLOSING 
在TCP四次挥手期间，主动关闭端发送了FIN包后，没有收到对应的ACK包，却收到对方的FIN包，此时，进入CLOSING状态。</p>
</li>
<li>
<p>CLOSE_WAIT 
在TCP四次挥手期间，被动关闭端收到FIN包后，进入CLOSE_WAIT状态。</p>
</li>
<li>
<p>LAST_ACK 
在TCP四次挥手时，被动关闭端发送FIN包后，进入LAST_ACK状态，等待对方的ACK包。</p>
</li>
</ul>
<p>主动连接端可能的状态有：<br />
CLOSED        SYN_SEND        ESTABLISHED。</p>
<p>主动关闭端可能的状态有：<br />
FIN_WAIT_1        FIN_WAIT_2        TIME_WAIT。</p>
<p>被动连接端可能的状态有：<br />
LISTEN        SYN_RECV        ESTABLISHED。</p>
<p>被动关闭端可能的状态有：
CLOSE_WAIT        LAST_ACK        CLOSED。</p>
<p>在Linux下，如果连接数比较大，可以使用效率更高的ss来替代netstat。</p>
<ul>
<li>标准的简单服务介绍</li>
<li>服务的开启</li>
</ul>
<h1 id="标准的简单服务介绍"><a class="header" href="#标准的简单服务介绍">标准的简单服务介绍</a></h1>
<p>有一些标准的简单服务几乎每种实现都要提供。这些简单的服务可以用来做网络实验。</p>
<table><thead><tr><th>名字</th><th>TCP端口号</th><th>UDP端口号</th><th>RFC</th><th>描述</th></tr></thead><tbody>
<tr><td>echo</td><td>7</td><td>7</td><td>862</td><td>服务器返回客户发送的所有内容</td></tr>
<tr><td>discard</td><td>9</td><td>9</td><td>863</td><td>服务器丢弃客户发送的所有内容</td></tr>
<tr><td>daytime</td><td>13</td><td>13</td><td>867</td><td>服务器以可读形式返回时间和日期</td></tr>
<tr><td>chargen</td><td>19</td><td>19</td><td>864</td><td>客户发送一个数据报时，TCP服务器发送一串连续的字符流，直到客户终端连接。<br/>UDP服务器发送一个随机长度的数据包</td></tr>
<tr><td>time</td><td>37</td><td>37</td><td>868</td><td>服务器返回一个二进制形式的32bit数，表示从UTC时间1900年1月1日午夜至今的秒数</td></tr>
</tbody></table>
<h1 id="服务的开启"><a class="header" href="#服务的开启">服务的开启</a></h1>
<p>在Ubuntu18.04及其以后的版本是通过安装xinetd获取这些服务的。</p>
<pre><code>apt-get install xinetd
</code></pre>
<p>安装完成后，在/etc/xinetd.d目录修改对应服务的配置文件来开启对应的服务</p>
<pre><code>root@jackson:/etc/xinetd.d# ll
total 56
drwxr-xr-x  2 root root 4096 Jun  8 05:54 ./
drwxr-xr-x 98 root root 4096 Jun  8 05:49 ../
-rw-r--r--  1 root root  640 Feb  5  2018 chargen
-rw-r--r--  1 root root  313 Feb  5  2018 chargen-udp
-rw-r--r--  1 root root  502 Feb  5  2018 daytime
-rw-r--r--  1 root root  313 Feb  5  2018 daytime-udp
-rw-r--r--  1 root root  390 Jun  8 05:50 discard
-rw-r--r--  1 root root  312 Feb  5  2018 discard-udp
-rw-r--r--  1 root root  420 Jun  8 05:54 echo
-rw-r--r--  1 root root  304 Feb  5  2018 echo-udp
-rw-r--r--  1 root root  312 Feb  5  2018 servers
-rw-r--r--  1 root root  314 Feb  5  2018 services
-rw-r--r--  1 root root  569 Feb  5  2018 time
-rw-r--r--  1 root root  313 Feb  5  2018 time-udp
</code></pre>
<p>如开启echo服务，需要在echo文件中把disable = yes 改成 disable = no</p>
<pre><code># default: off
# description: An xinetd internal service which echo's characters back to
# clients.
# This is the tcp version.
service echo
{
        disable         = no
        type            = INTERNAL
        id              = echo-stream
        socket_type     = stream
        protocol        = tcp
        user            = root
        wait            = no
}

# This is the udp version.
service echo
{
        disable         = no
        type            = INTERNAL
        id              = echo-dgram
        socket_type     = dgram
        protocol        = udp
        user            = root
        wait            = yes
}
</code></pre>
<p>udp协议的开启也可以在这个文件里修改，至于echo-udp文件，我倒是没关心验证过</p>
<p>修改完配置文件后，执行 </p>
<pre><code>systemctl restart xinetd
</code></pre>
<p>重新启动xinetd服务就行了，现在可以使用netstat命令来查看是否启动了7的端口</p>
<pre><code># netstat -antu
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN     
tcp        0      0 192.168.254.2:22        192.168.254.1:49656     ESTABLISHED
tcp6       0      0 :::7                    :::*                    LISTEN     
tcp6       0      0 :::9                    :::*                    LISTEN     
tcp6       0      0 :::22                   :::*                    LISTEN     
udp        0      0 127.0.0.53:53           0.0.0.0:*                          
udp        0      0 10.0.2.15:68            0.0.0.0:*                          
udp6       0      0 :::7                    :::*                           
</code></pre>
<p>现在tcp 和 upd 上的7端口都是开着</p>
<ul>
<li>
<p>udp协议头以及含义</p>
</li>
<li>
<p>udp的优点和缺点</p>
</li>
<li>
<p>udp的使用场景</p>
</li>
<li>
<p>tcp协议头以及含义</p>
</li>
<li>
<p>tcp的三次握手和四次挥手过程以及socket的状态变化</p>
</li>
<li>
<p>流量拥塞控制算法和其扩展用法</p>
</li>
<li>
<p>tcp的优点和缺点</p>
</li>
<li>
<p>tcp的使用场景</p>
</li>
<li>
<p>tcp和udp的对比</p>
</li>
<li>
<p>MTU</p>
</li>
</ul>
<h2 id="udp协议头以及含义"><a class="header" href="#udp协议头以及含义">UDP协议头以及含义</a></h2>
<p><img src="%E7%BD%91%E7%BB%9C/./img/udp_header.png" alt="UDP header" /></p>
<ul>
<li>8字节</li>
<li>0~15位表示 source port. 源端口号是用于标识发送端应用程序</li>
<li>16~31位表示 destination port. 用于标识接收端应用程序</li>
<li>32~47位表示 length. Length 的值是 UDP Header 的长度加上 UDP Payload 的长度. Payload 最大长度是 65536-8 = 65528 字节</li>
<li>48~61位表示 checksum. UDP 检验和是用于差错检测的，检验计算包含 UDP Header 和 UDP Data 两部分</li>
</ul>
<h2 id="udp的优点和缺点"><a class="header" href="#udp的优点和缺点">UDP的优点和缺点</a></h2>
<p>缺点:
UDP 是一个无连接的、不可靠的传输层协议，没有丢包重传机制、没有流控机制、没有拥塞控制机制.UDP 不保证数据包的顺序，UDP 传输经常出现乱序，UDP 不对重复包进行过滤.
优点:</p>
<ul>
<li>第一，UDP 是面向消息的传输协议，保证数据包的边界，不需要进行消息解析，处理逻辑非常简单；</li>
<li>第二，UDP 具有实时、高效的传输特性；</li>
<li>第三，协议栈没有对 UDP 进行过多的干预，这给应用层带来了很多方便之处，应用程序可以根据自己的需要对传输进行控制。比如，自己实现优先级控制、流量控制、可靠性机制等。</li>
</ul>
<h2 id="udp使用场景"><a class="header" href="#udp使用场景">UDP使用场景</a></h2>
<ol>
<li>多播通信的时候，只能通过 UDP 来实现。</li>
<li>UDP 在音视频会议、VOIP、音视频实时通信等行业有着广泛的应用。</li>
<li>在游戏领域，UDP也使用广泛，当网络环境很差时，TCP慢启动就不如UDP有效率了。</li>
</ol>
<p>tcp</p>
<ul>
<li>回复的RST报文</li>
<li>主动发送的RST报文</li>
</ul>
<p>设定一：server是192.168.254.2，client是192.168.254.3。server端设置了keep_alive选项，当然双方都可以keep_alive,这里为了简化模型，只设置server端。</p>
<p>设定二: 无论client如何断开，server端都不会调用close函数来关闭socket.</p>
<h1 id="回复的rst报文"><a class="header" href="#回复的rst报文">回复的RST报文</a></h1>
<p>client连接server后，强制终止程序,其交互过程如下:</p>
<pre><code>07:55:35.036880 IP 192.168.254.3.46256 &gt; jackson.6666: Flags [S], seq 2832717138, win 64240, options [mss 1460,sackOK,TS val 725485558 ecr 0,nop,wscale 7], length 0
07:55:35.036912 IP jackson.6666 &gt; 192.168.254.3.46256: Flags [S.], seq 3435299165, ack 2832717139, win 65160, options [mss 1460,sackOK,TS val 2992765659 ecr 725485558,nop,wscale 7], length 0
07:55:35.037573 IP 192.168.254.3.46256 &gt; jackson.6666: Flags [.], ack 1, win 502, options [nop,nop,TS val 725485558 ecr 2992765659], length 0
07:55:44.020872 IP 192.168.254.3.46256 &gt; jackson.6666: Flags [F.], seq 1, ack 1, win 502, options [nop,nop,TS val 725494542 ecr 2992765659], length 0
07:55:44.022028 IP jackson.6666 &gt; 192.168.254.3.46256: Flags [.], ack 2, win 510, options [nop,nop,TS val 2992774644 ecr 725494542], length 0
07:55:54.270656 IP jackson.6666 &gt; 192.168.254.3.46256: Flags [.], ack 2, win 510, options [nop,nop,TS val 2992784893 ecr 725494542], length 0
07:55:54.271430 IP 192.168.254.3.46256 &gt; jackson.6666: Flags [.], ack 1, win 502, options [nop,nop,TS val 725504793 ecr 2992774644], length 0
07:56:04.510594 IP jackson.6666 &gt; 192.168.254.3.46256: Flags [.], ack 2, win 510, options [nop,nop,TS val 2992795133 ecr 725504793], length 0
07:56:04.512667 IP 192.168.254.3.46256 &gt; jackson.6666: Flags [.], ack 1, win 502, options [nop,nop,TS val 725515034 ecr 2992774644], length 0
07:56:14.750275 IP jackson.6666 &gt; 192.168.254.3.46256: Flags [.], ack 2, win 510, options [nop,nop,TS val 2992805372 ecr 725515034], length 0
07:56:14.751019 IP 192.168.254.3.46256 &gt; jackson.6666: Flags [.], ack 1, win 502, options [nop,nop,TS val 725525274 ecr 2992774644], length 0
07:56:24.990241 IP jackson.6666 &gt; 192.168.254.3.46256: Flags [.], ack 2, win 510, options [nop,nop,TS val 2992815612 ecr 725525274], length 0
07:56:24.991256 IP 192.168.254.3.46256 &gt; jackson.6666: Flags [.], ack 1, win 502, options [nop,nop,TS val 725535514 ecr 2992774644], length 0
07:56:35.230065 IP jackson.6666 &gt; 192.168.254.3.46256: Flags [.], ack 2, win 510, options [nop,nop,TS val 2992825852 ecr 725535514], length 0
07:56:35.230900 IP 192.168.254.3.46256 &gt; jackson.6666: Flags [.], ack 1, win 502, options [nop,nop,TS val 725545754 ecr 2992774644], length 0
07:56:45.470659 IP jackson.6666 &gt; 192.168.254.3.46256: Flags [.], ack 2, win 510, options [nop,nop,TS val 2992836093 ecr 725545754], length 0
07:56:45.471338 IP 192.168.254.3.46256 &gt; jackson.6666: Flags [R], seq 2832717140, win 0, length 0
</code></pre>
<p>我在07:55:44.020872强制终止了程序，但是后边server发送的心跳包还是能收到client的ack的，这是tcp/ip内核协议栈的实现，应用层无法控制。client终止后，双方的socket状态如下:</p>
<p>server:</p>
<pre><code>tcp        0      0 0.0.0.0:6666            0.0.0.0:*               LISTEN
tcp        0      0 192.168.254.2:6666      192.168.254.3:46258     CLOSE_WAIT
</code></pre>
<p>client:</p>
<pre><code>tcp        0      0 192.168.254.3:46258     192.168.254.2:6666      FIN_WAIT2
</code></pre>
<h1 id="主动发送的rst报文"><a class="header" href="#主动发送的rst报文">主动发送的RST报文</a></h1>
<p>client连接server后，关闭client的网卡，server发送给client的检测报文就不能收到ack了，然后尝试3次就主动发送了RST报文，关闭的连接。</p>
<pre><code>08:05:15.167208 IP jackson.6666 &gt; 192.168.254.3.46264: Flags [.], ack 1, win 510, options [nop,nop,TS val 2993345789 ecr 726055552], length 0
08:05:20.286383 IP jackson.6666 &gt; 192.168.254.3.46264: Flags [.], ack 1, win 510, options [nop,nop,TS val 2993350909 ecr 726055552], length 0
08:05:25.407155 IP jackson.6666 &gt; 192.168.254.3.46264: Flags [.], ack 1, win 510, options [nop,nop,TS val 2993356029 ecr 726055552], length 0
08:05:30.526367 IP jackson.6666 &gt; 192.168.254.3.46264: Flags [R.], seq 1, ack 1, win 510, options [nop,nop,TS val 2993361149 ecr 726055552], length 0</code></pre>
<ul>
<li>tcp自带的keepAlive的优点与缺点</li>
<li>应用层实现的心跳机制的优点和缺点</li>
</ul>
<p>如何保持连接,检测连接是否正常,异常情况下连接断开如何重新连上是个非常重要的课题,为什么说很重要了有下面几个因素:</p>
<ul>
<li>网络状况复杂,连接会经常中断,比如出入电梯</li>
<li>很多防火墙路由器等对于空闲socket自动关闭</li>
<li>对于非正常断开, 服务器并不能检测到. 为了回收资源, 必须提供一种检测机制.</li>
</ul>
<h1 id="tcp自带的keepalive的优点与缺点"><a class="header" href="#tcp自带的keepalive的优点与缺点">tcp自带的keepAlive的优点与缺点</a></h1>
<p>优点：使用起来简单, 减少了应用层代码的复杂度. 也会更节省流量, 因为应用层的数据传输到TCP协议层时都会被加上额外的包头包尾. 由TCP协议提供的检活, 其发的探测包, 理论上实现的会更精妙, 耗费更少的流量.</p>
<p>致命缺点:第一点,keepAlive只能检测连接存活,而不能检测连接可用,比如某台服务器因为某些原因导致负载超高,CPU满了,无法响应任何业务请求，但是使用 TCP 探针则仍旧能够确定连接状态，这就是典型的连接活着但业务提供方已死的状态，对客户端而言，这时的最好选择就是断线后重新连接其他服务器，而不是一直认为当前服务器是可用状态，一直向当前服务器发送些必然会失败的请求;第二点,如果tcp连接的另一端突然掉线，这个时候我们并不知道网络已经关闭。而此时，如果有发送数据失败，tcp会自动进行重传。重传包的优先级高于keepalive的包，那就意味着，我们的keepalive总是不能发送出去。 而此时，我们也并不知道该连接已经出错而中断。在较长时间的重传失败之后，我们才会知道。</p>
<h1 id="应用层实现的心跳机制的优点和缺点"><a class="header" href="#应用层实现的心跳机制的优点和缺点">应用层实现的心跳机制的优点和缺点</a></h1>
<p>优点:有着更大的灵活性，可以控制检测时机，间隔和处理流程，甚至可以在心跳包上附带额外信息,最重要的是可以做到没有上面所说的致命缺点,不光可以检测连接存在,还可以检测连接可用。</p>
<p>缺点:需要自己实现,有额外的工作量</p>
<h1 id="tcp的连接和建立"><a class="header" href="#tcp的连接和建立">tcp的连接和建立</a></h1>
<p>使用telnet连接远程的discard服务，我们来看一看通信过程:
# telnet 192.168.254.2 discard
Trying 192.168.254.2...
Connected to 192.168.254.2.
Escape character is '^]'.
^]</p>
<pre><code>telnet&gt; q
Connection closed.
</code></pre>
<p>在执行上面的过程之前，在其中的任意一台机器上执行tcpdump命令来捕获整个通信过程</p>
<pre><code>tcpdump -i enp0s8 port 9
</code></pre>
<p>下面是tcpdump输出的通信过程:</p>
<pre><code>11:17:35.139285 IP 192.168.254.1.55358 &gt; jackson.discard: Flags [S], seq 2028271312, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
11:17:35.139318 IP jackson.discard &gt; 192.168.254.1.55358: Flags [S.], seq 1856125507, ack 2028271313, win 64240, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
11:17:35.140750 IP 192.168.254.1.55358 &gt; jackson.discard: Flags [.], ack 1856125508, win 8212, length 0
11:17:39.562183 IP 192.168.254.1.55358 &gt; jackson.discard: Flags [F.], seq 2028271313, ack 1856125508, win 8212, length 0
11:17:39.562562 IP jackson.discard &gt; 192.168.254.1.55358: Flags [F.], seq 1856125508, ack 2028271314, win 502, length 0
11:17:39.562977 IP 192.168.254.1.55358 &gt; jackson.discard: Flags [.], ack 1856125509, win 8212, length 0
</code></pre>
<p>对于TCP段，每个输出行开始按如下格式显示:</p>
<h2 id="时间-四元组"><a class="header" href="#时间-四元组">时间 四元组</a></h2>
<p>时:分:秒.微妙 IP 源.port &gt; 目的.port: </p>
<h2 id="flags"><a class="header" href="#flags">Flags</a></h2>
<p>Flags [标志]</p>
<p>标志代表TCP首部中6个标志比特中的4个。如下图所示</p>
<table><thead><tr><th>标志</th><th>3字符缩写</th><th>描述</th></tr></thead><tbody>
<tr><td>S</td><td>SYN</td><td>同步序号</td></tr>
<tr><td>F</td><td>FIN</td><td>发送方完成数据发送</td></tr>
<tr><td>R</td><td>RST</td><td>复位连接</td></tr>
<tr><td>P</td><td>PSH</td><td>尽可能快地将数据送往接受进程</td></tr>
<tr><td>.</td><td></td><td>以上四个标志比特均置为0</td></tr>
</tbody></table>
<h2 id="seq"><a class="header" href="#seq">seq</a></h2>
<p>seq 4257468354[:4257468374] 表示分组的序号是4257468354，当传输的字节数不为0时，可能会有冒号和其后的隐含的结尾序号。</p>
<p>seq字段满足如下两个条件时才会出现</p>
<ol>
<li>报文段中至少包含一个数据字节</li>
<li>SYN,FIN或RST被设置为1时</li>
</ol>
<h2 id="ack"><a class="header" href="#ack">ack</a></h2>
<p>ack 1856125508 表示确认序号，表示我已经收到了多少ack-1的包，我现在想接受序号为ack的包。它只有在首部中的ACK标志置为1时才显示</p>
<h2 id="win"><a class="header" href="#win">win</a></h2>
<p>win 8212 表示发送端通告的窗口大小，每个报文段中都有</p>
<h2 id="mss-option"><a class="header" href="#mss-option">mss option</a></h2>
<p>mss 1460 表示发送端指明的最大报文段长度。发送端将不接收超过这个长度的TCP报文段.</p>
<p>日期 2021-06-11</p>
<ul>
<li>CPU
<ul>
<li>CPU 主频，核心，缓存</li>
<li>Intel CPU系列</li>
<li>Intel CPU后缀 </li>
</ul>
</li>
<li>GPU
<ul>
<li>核显与独显</li>
<li>显卡的型号、显存、显卡核心频率</li>
</ul>
</li>
<li>内存条
<ul>
<li>DDR3 DDR4 DDR5</li>
<li>8G * 2 与 16G</li>
</ul>
</li>
<li>主板
<ul>
<li>主板作用</li>
<li>主板类型</li>
<li>主板与CPU</li>
<li>主板与GPU</li>
<li>主板与内存条</li>
</ul>
</li>
</ul>
<p>装机模拟助手 https://mydiy.pconline.com.cn/</p>
<h1 id="cpu"><a class="header" href="#cpu">CPU</a></h1>
<h2 id="cpu-主频核心缓存"><a class="header" href="#cpu-主频核心缓存">CPU 主频，核心，缓存</a></h2>
<p>主频表示的是CPU的时钟频率，时钟频率越高，当然指令处理的越快。</p>
<p>核心表示是并发处理能力，目前CPU都是1 core 1 processor 和 1 core 2 processor。每个线程(processor)都能单独执行程序</p>
<p>主频是作用于整个CPU的，主频高，每个processor执行的就快。</p>
<p>相同的价位可以获取不同类型的&lt;主频，核心&gt;CPU,主频高点，核心少点，主频低点，核心多点。该如何抉择呢? 这要看具体的工作是什么了，如果是办公或者需要大量并行图形渲染的工作来说，当然是选择核心多，主频低型的CPU，如果是玩游戏这种单一任务，但是需要强大的计算能力,这就适合选择主频高，核心少型的CPU(当然不能太少，最起码得是2核4线程或者4核4线程的)</p>
<p>简单的对主频分一下类: &gt;= 3.6GHz可以看作高端CPU,可以用来流畅的玩各种游戏, &lt; 3.6GHz可以看作中低端CPU,适合作为办公使用。
核心就按照 4核/4线程来作为分界线吧</p>
<p>缓存虽然也是一个重要的参数，但是主频和核心选好了，缓存配套的应该也不会差。只有当价格差不多，主频和核心一致时，才拿缓存这个参数来说事吧。</p>
<h2 id="intel-cpu系列"><a class="header" href="#intel-cpu系列">Intel CPU系列</a></h2>
<ul>
<li>Xeon 志强 主要用于服务器，性能最好</li>
<li>Core 酷睿 酷睿是中高端定位 主要用于台式机和笔记本</li>
<li>Pentium 奔腾 入门级定位，主要用于台式机和笔记本</li>
<li>Celeron 赛扬 低端定位，主要用于台式机和笔记本</li>
<li>Atom 凌动 主要用于移动平台，功耗小</li>
</ul>
<h2 id="intel-cpu后缀"><a class="header" href="#intel-cpu后缀">Intel CPU后缀</a></h2>
<p>有后缀的CPU大部分都是笔记本使用的CPU.</p>
<ul>
<li>M代表标准电压cpu是可以拆卸的</li>
<li>U代表低电压节能的，可以拆卸的；</li>
<li>H是高电压的，是焊接的，不能拆卸；</li>
<li>X代表高性能，可拆卸的；</li>
<li>Q“Q”是“Quad”的缩写，即四核CPU；</li>
<li>Y代表超低电压的，除了省电，没别的优点的了，是不能拆卸的；</li>
</ul>
<p>两个字母的，属于上面这些字母的组合，比如：HQ，表示高电压四核CPU。</p>
<p>M：笔记本专用CPU，表示标准电压双核移动版本。一般为双核，M前面一位数字是0，意味着是标准电压处理器，如果是7，则是低电压处理器。</p>
<p>U：笔记本专用低电压CPU，主要用在超级本上，主频很低且一般为双核处理器。U前面一位数字为8，则是28W功耗的低压处理器（标准电压双核处理器功耗为35W），若前一位数字为7，则是17W功耗的低压处理器，若为0，则是15W功耗的低压处理器。</p>
<p>QM、MQ（QM是三代以前包括三代四核i7的后缀，到第四代QM改为MQ）：笔记本专用CPU，“Q”是“Quad”的缩写，即四核CPU。若QM前一位数字是0，则表示此产品为功耗45W的标准电压四核处理器，若为2，则表示此产品为35W功耗的低电压四核处理器，若为5，与对应为0的CPU主要规格相同，但集成的核芯显卡频率更高（如3630QM和3635QM，后者核显最大频率1.2GHz，前者则是1.15GHz）。</p>
<p>HQ：这个后缀表示CPU核显加强且核心不是一般的Haswell。HQ是四核焊接版，Q是英文“Quad”的缩写，意思是四，代表四核。第四代CPU新出现的系列，主要参数和标准的四核CPU一致，但集成了性能空前强大的核芯显卡Iris Pro5200系列，这种核显的性能可以直接媲美中端独立显卡。目前有i7 4750HQ，4850HQ和4950HQ三款CPU，后来出了一款i7 4702HQ，并没有集成高性能核芯显卡，是定位较为模糊的一款产品。</p>
<p>XM：XM是笔记本旗舰四核i7的后缀，是最强大的笔记本CPU，功耗一般为55W。有这个后缀的笔记本i7相比MQ或QM版本多了2MB三级缓存并支持自由超频技术。“X”意为“Extreme”，此类型CPU完全不锁频，在散热和供电允许的情况下可以无限制超频，而即便是默认频率下，也比同一时代的其它产品强大得多。这类CPU都是工厂生产后精心挑选出来得极品，质量极佳，性能完美，但价格非常昂贵。一块XM系列的CPU批发价可达1000美金以上（普通的四核大概3，400美金一块）。</p>
<p>PM：这个后缀表示CPU没有核显</p>
<p>台式CPU标准款一般没有后缀字母，若有后缀字幕“K”，则是可以超频的版本，若有后缀字幕“X”，则是顶级的至尊版（台式机至尊版CPU为6核心12线程）。</p>
<h1 id="gpu"><a class="header" href="#gpu">GPU</a></h1>
<h2 id="核显与独显"><a class="header" href="#核显与独显">核显与独显</a></h2>
<h2 id="显卡的型号显存显卡核心频率"><a class="header" href="#显卡的型号显存显卡核心频率">显卡的型号、显存、显卡核心频率</a></h2>
<p>Nvidia (N卡): GTX系 与 RTX系 Titan RTX系
AMD (A卡): R7,R9,RX</p>
<p>N卡GTX系，RTX系与Titan RTX系区别:</p>
<p>GTX代表高端或顶级显卡，原来还有更低级系列的，只是现在没有了，但是10系列还是沿用了GTX。</p>
<p>到了20系列，显卡引入光追新技术，所以GTX改名成RTX，RT即ray tracing(光线追踪)，来表示显卡的光追功能。</p>
<p>Titan RTX算是RTX的一个分支吧，主要在工作站上使用，和RTX系使用相同的图灵架构，但是参数上加强了，Titan系代表当今最强显卡。</p>
<p>N卡Ti和Super后缀的区别:</p>
<p>同一系列Ti后缀的是最强版本，然后阉割一下变成Super版本，Super与Ti差别不是很大，然后再阉割一下就变成没有后缀的版本了。Ti版本才是本体，后边的只是降低性能，适应不同价位的市场。如GTX1660Ti,GTX1660Super,GTX1660。GTX1660Ti性能上非常接近RTX2060卡，但是差了一个光追功能。</p>
<p>买显卡主要看型号，显存和显卡核心频率等参数都是和型号相关的，型号是高端的，显存自然就大，频率就高。这里以一个大概2000价位的中高端显卡GTX1660Ti系来作为比较基线吧
参数名      | 参数
------------|--------------
显卡型号    | GTX 1660 Ti
核心代号	| TU116-400
制造工艺(nm)| 12nm
流处理器/RT核心/Tensor核心|1536/0/0
核心频率(MHz)|1500
加速频率(MHz)|1770
显存位宽(-bit)|192
显存容量|6GB GDDR6
显存频率(GHz)|12
整卡功耗(W)|120</p>
<p>这里说一下显存类型，显存是基于内存改造的，如GDDR5显存是基于DDR3内存改造而来，GDDR6基于DDR4内存技术。</p>
<h1 id="内存条"><a class="header" href="#内存条">内存条</a></h1>
<h2 id="ddr3-ddr4-ddr5"><a class="header" href="#ddr3-ddr4-ddr5">DDR3 DDR4 DDR5</a></h2>
<p>DDRx x表示的是第几代内存条，如DDR4就是第4代内存条，每一代频率都会分为3个档次,以DDR4为例频率主要2666、3200、3600三种，性价比最高的是3200MHZ</p>
<h2 id="8g--2-与-16g"><a class="header" href="#8g--2-与-16g">8G * 2 与 16G</a></h2>
<p>结论就是两条8G内存条要比一个16G内存条性能好，因为是双通道访问.</p>
<h1 id="主板"><a class="header" href="#主板">主板</a></h1>
<p>主板主要是和CPU适配，主要是intel CPU, intel CPU的插槽类型最多变。下面是一个简单的表格,展示的Intel 最近系列的CPU使用的插槽类型。</p>
<table><thead><tr><th>插槽</th><th>CPU系</th></tr></thead><tbody>
<tr><td>LGA1200</td><td>全11代，大部分10代。以后不用了</td></tr>
<tr><td>LGA2066</td><td>小部分10代</td></tr>
<tr><td>LGA1151</td><td>9代，8代</td></tr>
</tbody></table>
<p>更多关于CPU插槽的发展史可以参考这个:http://www.elecfans.com/d/1472891.html</p>
<p>......</p>

                    </main>

                    <nav class="nav-wrapper" aria-label="Page navigation">
                        <!-- Mobile navigation buttons -->
                        

                        

                        <div style="clear: both"></div>
                    </nav>
                </div>
            </div>

            <nav class="nav-wide-wrapper" aria-label="Page navigation">
                

                
            </nav>

        </div>

        
        <!-- Livereload script (if served using the cli tool) -->
        <script type="text/javascript">
            var socket = new WebSocket("ws://localhost:3000/__livereload");
            socket.onmessage = function (event) {
                if (event.data === "reload") {
                    socket.close();
                    location.reload();
                }
            };

            window.onbeforeunload = function() {
                socket.close();
            }
        </script>
        

        

        

        
        <script type="text/javascript">
            window.playground_copyable = true;
        </script>
        

        

        
        <script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="searcher.js" type="text/javascript" charset="utf-8"></script>
        

        <script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="highlight.js" type="text/javascript" charset="utf-8"></script>
        <script src="book.js" type="text/javascript" charset="utf-8"></script>

        <!-- Custom JS scripts -->
        

        
        
        <script type="text/javascript">
        window.addEventListener('load', function() {
            window.setTimeout(window.print, 100);
        });
        </script>
        
        

    </body>
</html>
